Save todos in localStorage - javascript

I've created a todo app, and I want to save the todo's in localStorage.
I was thinking to add the localStorage property like this:
addForm.addEventListener("submit", (e) => {
e.preventDefault();
let todo = addForm.add.value.trim();
if (todo.length) {
localStorage.setItem(todo);
generateTemplate(todo);
addForm.reset();
}
}
But, it's not working.
So, my question is, in which part of the code is it best practice to add the property and in which part is it best to also add the getItem method?
Here is the whole code without the localStorage property:
const addForm = document.querySelector(".add");
const list = document.querySelector(".todos");
const search = document.querySelector(".search input");
// generate new toDo's
const generateTemplate = (todo) => {
let html = ` <li class="list-group-item d-flex justify-content-between align-items-center">
<span>${todo}</span><i class="far fa-trash-alt delete"></i>
</li>`;
list.innerHTML += html;
};
// submit the todo
addForm.addEventListener("submit", (e) => {
e.preventDefault();
let todo = addForm.add.value.trim();
if (todo.length) {
generateTemplate(todo);
addForm.reset();
}
});
// delete todo's
list.addEventListener("click", (e) => {
if (e.target.classList.contains("delete")) {
e.target.parentElement.remove();
}
});
// filter the toDo's
const filterTodos = (term) => {
Array.from(list.children)
.filter((todo) => !todo.textContent.toLowerCase().includes(term))
.forEach((todo) => todo.classList.add("filtered"));
Array.from(list.children)
.filter((todo) => todo.textContent.toLowerCase().includes(term))
.forEach((todo) => todo.classList.remove("filtered"));
};
search.addEventListener("keyup", () => {
const term = search.value.trim().toLowerCase();
filterTodos(term);
});
body {
background-color: #352f5b;
}
.container {
max-width: 400px;
}
input[type="text"],
input[type="text"]:focus {
color: #fff;
border: none;
background: rgba(0, 0, 0, 0.2);
max-width: 400px;
}
.todos li {
background: #423a6f;
}
.delete {
cursor: pointer;
}
.filtered {
display: none !important;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>toDoList</title>
<script src="https://kit.fontawesome.com/fa5117c01c.js" crossorigin="anonymous"></script>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous" />
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container">
<header class="text-center text-light my-4">
<h1 class="text-ligth mb.4">thingsToDo</h1>
<form action="" class="search"><input type="text" class="form-control m-auto" name="search" placeholder="Search toDo's"></form>
</header>
<ul class="list-group todos mx-auto text-light"></ul>
<form class="add text-center my-4">
<label class="text-light">Add new toDo...</label>
<input type="text" class="form-control m-auto" name="add">
</form>
</div>
<script src="index.js"></script>
</body>
</html>

Storages object (what you use with LocalStorage) allows to save Strings (DOMStrings to be specific), so if you want to save a javascript object, you should convert it to string first (eg. with JSON.stringify)
On your code, also, the syntax for setItem is incorrect. You need to specify a key that identifies the object
localStorage.setItem('todo', todo);
Notice that this will only save a todo. Therefore, when you get todo's back you will need to do localStorage.getItem('todo') and this of course will only return the last todo that you have saved.
A solution for this might be to have a storage for your todos:
var todos = []
Everytime you create a new todo you add that todo to this object
todos.push(todo)
and saves it to localstorage
localStorage.setItem('todos', JSON.stringify(todos))
When you get back that list, you will need to parse the string back to get the actual object:
var todosString = localStorage.getItem('todos');
var todos = JSON.parse(todosString);
Take into account that this will only get your todos data into that var. Then you need to repopulate your dom.
This can be done by reusing your generateTemplate function.
Eg.
todos.forEach(todo => generateTemplate(todo));
I'd also recommend you to take a look at the MDN page for LocalStorage: https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
And the Using the Web Storage API page for a full working example: https://developer.mozilla.org/en-US/docs/Web/API/Web_Storage_API/Using_the_Web_Storage_API

Related

How to remove items on my todo list with local storage by clicking on one button

I am practising on a todolist. I would like to remove the items from the list with a button in one go . When I click the button Clear the second if of containerList.addEventListener manages to delete all the items from the local storage. Unfortunately I can't manage to target the <li> in the DOM and remove the same items there too, unless I refresh...
How to target and remove them? I have also tried to target `list.children.remove()' but there is an error saying "it is not a function". Plese help. Thank you
const addForm = document.querySelector('.add');
const list = document.querySelector('.todos');
const clearAll = document.querySelector('.bottomForm');
const containerList = document.querySelector('.containerList');
// salvato gli items dal local storage in una variabile
let storedItems = localStorage.getItem('tasks');
const generateTemplate = todo => {
const html = `
<li class="newList">
<div class="tick">
<input type="checkbox" id="" name="" value="">
</div>
<div class="content">
<span>${todo}</span>
<i class="fas fa-times delete"></i>
</div>
</li>`
list.innerHTML += html;
}
if (!storedItems) {
storedItems = [];
} else {
storedItems = JSON.parse(storedItems);
storedItems.forEach(item => {
generateTemplate(item);
});
}
addForm.addEventListener('submit', e => {
const todo = addForm.add.value.trim();
e.preventDefault();
if (todo.length) {
generateTemplate(todo);
storedItems.push(todo);
localStorage.setItem('tasks', JSON.stringify(storedItems))
addForm.reset();
console.log(`${todo} has been added to html list`)
console.log(`Local storage now contains ${storedItems}`)
document.getElementById("numberItems").innerHTML = `${storedItems.length} item(s) `; //counting element when added
console.log(`aftr adding, now the items are ${storedItems.length}`)
}
});
/*Removing item*/
containerList.addEventListener('click', e => {
console.log(e.target);
if (e.target.classList.contains('delete')) {
e.target.parentElement.parentElement.remove();
let removedItem = e.target.parentElement.firstElementChild.innerText;
//console.log(`${removedItem} has been removed from the html list`);
//console.dir(e.target.parentElement.firstElementChild.innerText);
//console.log(storedItems)
const newArr = storedItems.filter(item => item !== removedItem)
//console.log(newArr)
storedItems = newArr
localStorage.setItem('tasks', JSON.stringify(storedItems))
document.getElementById("numberItems").innerHTML = `${storedItems.length} item(s) `; //counting element when deleted
//console.log(`Local storage now contains ${storedItems} `)
//console.log(`after removing, now the items are ${storedItems.length}`)
}
if (e.target.classList.contains('clears')){
window.localStorage.removeItem('tasks');
console.log('clear button has been pushed')
document.getElementsByClassName("newList").innerHTML = ''
}
})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- displays site properly based on user's device -->
<link rel="icon" type="image/png" sizes="32x32" href="./images/favicon-32x32.png">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
<title>Frontend Mentor | Todo app</title>
<!-- Feel free to remove these styles or customise in your own stylesheet 👍 -->
<!-- <style>
.attribution { font-size: 11px; text-align: center; }
.attribution a { color: hsl(228, 45%, 44%); }
</style> -->
<link rel="stylesheet" href="style.css">
</head>
<body>
<!--
Todo
items left
All
Active
Completed
Clear Completed
Drag and drop to reorder list -->
<div class="mainTitle">
<h1>TO DO</h1>
</div>
<form action="" class="add">
<div class="inputE">
<input type="text" id="name" name="add" placeholder="Enter name here">
</div>
<div class="containerList">
<ul class="todos"></ul>
<div class="bottomForm">
<div class="itemsLeft">
<p><span id="numberItems"></span> left</p> <!--use if for items or item !-->
</div>
<div class="allItems">
<button onclick="myFunction()">All</button>
</div>
<div class="activeItems">
<button onclick="myFunction()">Active</button>
</div>
<div class="completedItems">
<button onclick="myFunction()">Completed</button>
</div>
<div class="clear">
<button onclick="" class="clears">Clear</button>
</div>
<div class="completedItems">
<button onclick="myFunction()">Completed</button>
</div>
</div>
</div>
</div>
</form>
<!-- <div class="attribution">
Challenge by Frontend Mentor.
Coded by Your Name Here.
</div> -->
<script src="app.js"></script>
</body>
</html>
You were close with list.children.remove()! The problem is, that the children properties holds a HTMLCollection of elements, which does not support remove() itself. You can iterate over that HTMLCollection with a loop and call remove() on every child element:
for (const child of list.children) {
child.remove()
}
Alternativly you could convert it to a array and use the typical array functions, if you are more familiar with those:
[...list.children].forEach(child => child.remove())
The challenge was to target and remove all the children of .list. list.children.remove() did not work because
the children properties holds a HTMLCollection of elements, which does not support remove()
as #Julian Adler stated.
So I have used childNodes property in the for loop to remove all the items of the list (
in reality all the nodes, including white spaces and <i>):
`for (let i= list.childNodes.length -1; i>=0; i--) {
list.childNodes[i].remove()
}`
const addForm = document.querySelector('.add');
const list = document.querySelector('.todos');
const containerList = document.querySelector('.containerList');
let storedItems = localStorage.getItem('tasks');
const generateTemplate = todo => {
const html = `
<li class="newList">
<div class="tick">
<input type="checkbox" id="" name="" value="">
</div>
<div class="content">
<span>${todo}</span>
<i class="fas fa-times delete"></i>
</div>
</li>`
list.innerHTML += html;
}
if (!storedItems) {
storedItems = [];
} else {
storedItems = JSON.parse(storedItems);
storedItems.forEach(item => {
generateTemplate(item);
});
}
addForm.addEventListener('submit', e => {
const todo = addForm.add.value.trim();
e.preventDefault();
if (todo.length) {
generateTemplate(todo);
storedItems.push(todo);
localStorage.setItem('tasks', JSON.stringify(storedItems))
addForm.reset();
console.log(`${todo} has been added to html list`)
console.log(`Local storage now contains ${storedItems}`)
document.getElementById("numberItems").innerHTML = `${storedItems.length} item(s) `; //counting element when added
console.log(`aftr adding, now the items are ${storedItems.length}`)
}
});
/*Removing item*/
containerList.addEventListener('click', e => {
console.log(e.target);
if (e.target.classList.contains('delete')) {
e.target.parentElement.parentElement.remove();
let removedItem = e.target.parentElement.firstElementChild.innerText;
const newArr = storedItems.filter(item => item !== removedItem)
storedItems = newArr
localStorage.setItem('tasks', JSON.stringify(storedItems))
document.getElementById("numberItems").innerHTML = `${storedItems.length} item(s) `; //counting element when deleted
${storedItems.length}`)
}
if (e.target.classList.contains('clears')){
window.localStorage.removeItem('tasks');
console.log('clear button has been pushed')
for (let i= list.childNodes.length -1; i>=0; i--) {
list.childNodes[i].remove()
}
}
})

<script src="./script.js"></script> (Uncaught SyntaxError: Unexpected end of input)

**screenshot of the error
Hello everyone,
I am a complete beginner in coding and have been coding along with a webinar when I received the error: Uncaught SyntaxError: Unexpected end of input. I included a screenshot of the error. This happened I think when I added the inputChange function. The instructor of the webinar did not have any errors, and my code (so far) is identical to his code during the video.
Anybody who can help me understand and solve this issue?
Here is my code:
//Variables, Arrays, and Objects, dotNotation, bracketNotation
//Dom manipulation
let items = [
{
name: 'Ironhack T',
price: 10,
image: 'https://miro.medium.com/max/5190/1*aVsUjp1zvlRb1799gDjbLA#2x.jpeg'
},
{
name: 'Ironhack Hoodie',
price: 15,
image: 'https://m.media-amazon.com/images/I/B1i3u9-Q-KS._AC_CLa%7C2140%2C2000%7CB1wqstnnTfS.png%7C0%2C0%2C2140%2C2000%2B0.0%2C0.0%2C2140.0%2C2000.0_UL1500_.png'
},
{
name: 'Ironhack Sticker',
price: 2,
image:'https://e7.pngegg.com/pngimages/887/803/png-clipart-ironhack-web-development-job-startup-company-design-blue-user-interface-design-thumbnail.png'
},
{
name: 'Ironhack Mug',
price: 8,
image: 'https://d0bb7f9bf11b5ad1a6b2-6175f06f5e3f64e15abbf67415a276ec.ssl.cf1.rackcdn.com/product-images/designlab/11-oz-traditional-ceramic-coffee-mugs-7102-white1582888132.jpg'
},
];
let list = document.querySelector('ul');
items.forEach((item, i) =>{
console.log(item.name);
list.innerHTML += `<li>
<div>Name: ${item.name}</div>
<div>Price: $${item.price}</div>
<img src="${item.image}" />
<input type='number' placeholder='quantity' onchange='inputChange(${i}, '${item.name}', '${item.price}')' />
<button>Buy item</button>
</li>`
});
function inputChange(i, name, price){
console.log('I want to buy the ',i,' item named, ',name,' that costs $',price);
};
*{
transition: all 1s;
}
body{
padding: 10px;
background-color: lightseagreen;
}
section{
display: flex;
flex-direction: row;
}
img {
width: 50px;
}
#cart{
background-color:salmon;
}
#cart, #items{
width: 50vw;
}
h1{
color:#7c32ff;
}
/*selecting a tag*/
p{
color:green;
text-decoration: line-through;
}
/*Ids have hashtags*/
#two {
background-color:rebeccapurple;
}
/*classes have dots*/
.example {
border: 5px dashed purple;
margin: 5px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!--Now my CSS is linked to my html-->
<link href="/favicon.ico" type="image/x-icon" rel="icon" />
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<section>
<div>
<h2>Items</h2>
<ul id="items">
</ul>
</div>
<div>
<h2>Cart <span id="grandTotal">$0</span></h2>
<ul id="cart">
</ul>
</div>
</section>
<!-- <div class="example">
This is a div
</div>
<div class="example" id="two">
This is a div 2
</div>
<div class="example">
This is a div 3
</div>
<i>This will be italic text</i>
<b>This will be bold text</b>
<h1>This is a certain size</h1>
<p>This is for paragraphs</p> -->
<script src="./script.js"></script>
</body>
</html>
Edit: Fixing the Single (') and Double (") Quotes in the following code is enough
Notice the double quotes wrapping the variables passed to inputChange. And notice the single quotes wrapping the complete value passed to the onchange='...' attribute
// Inside the forEach loop
list.innerHTML += `<li>
<div>Name: ${item.name}</div>
<div>Price: $${item.price}</div>
<img src="${item.image}" />
<input type='number' placeholder='quantity' onchange='inputChange("${i}", "${item.name}", "${item.price}")' />
<button>Buy item</button>
</li>`;
Edit: Alternative solution which logs the current value of input additionally
While debugging, I took another solution, which also logs the current value of the input elements (i.e. how much the customer wants to buy).
Adding only an input element for each item, with an id property for getting it later when the change event fires.
Adding event listeners with .addEventListener("change", inputChange) - inputChange will receive an event object each time the input element is changed.
inputChange is the event handler function. It extracts the input element's id, and uses it to find the data (object in items array) with the Array.prototype.find() (More on this prototype https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)
// script.js
//Variables, Arrays, and Objects, dotNotation, bracketNotation
//Dom manipulation
let items = [
{
name: "Ironhack T",
price: 10,
image: "https://miro.medium.com/max/5190/1*aVsUjp1zvlRb1799gDjbLA#2x.jpeg",
},
{
name: "Ironhack Hoodie",
price: 15,
image:
"https://m.media-amazon.com/images/I/B1i3u9-Q-KS._AC_CLa%7C2140%2C2000%7CB1wqstnnTfS.png%7C0%2C0%2C2140%2C2000%2B0.0%2C0.0%2C2140.0%2C2000.0_UL1500_.png",
},
{
name: "Ironhack Sticker",
price: 2,
image:
"https://e7.pngegg.com/pngimages/887/803/png-clipart-ironhack-web-development-job-startup-company-design-blue-user-interface-design-thumbnail.png",
},
{
name: "Ironhack Mug",
price: 8,
image:
"https://d0bb7f9bf11b5ad1a6b2-6175f06f5e3f64e15abbf67415a276ec.ssl.cf1.rackcdn.com/product-images/designlab/11-oz-traditional-ceramic-coffee-mugs-7102-white1582888132.jpg",
},
];
/* Adds id property to objects inside items array */
items = items.map((item) => ({
...item,
id: item.name.replace(" ", "-").toLowerCase(),
}));
/* inputChange receives event object when the input changes */
function inputChange(event) {
// for inspection - log the event object
// console.log(event);
const { id } = event.target; // the id I gave to the input
const number = event.target.value; // the current value of the input
// find the object with the corresponding data in the items array
const data = items.find((item) => item.id === id);
console.log(
`I want to by ${number} of the item named, ${data.name} that costs $${data.price}`
);
}
let list = document.querySelector("ul");
items.forEach((item, i) => {
/* prettier-ignore */
list.innerHTML += `<li>
<div>Name: ${item.name}</div>
<div>Price: $${item.price}</div>
<img src="${item.image}" />
<input type='number' placeholder='quantity' id=${item.id} />
<button>Buy item</button>
</li>`;
});
/* Get all inputs */
const inputs = document.querySelectorAll("input");
inputs.forEach((inputEl) => inputEl.addEventListener("change", inputChange));
Additionally, one word concerning file paths in src property of script tag:
Either solution is fine in this case:
<script src="script.js"></script>
<script src="./script.js"></script>
your code ./script.js are incorrect.
this is some example of HTML File Paths:
<srcipt src="script.js"> The "script.js" file is located in the same folder as the current page
<srcipt src="js/script.js"> The "script.js" file is located in the js folder in the current folder
<srcipt src="/js/script.js"> The "script.js" file is located in the js folder at the root of the current web
<srcipt src="../script.js"> The "script.js" file is located in the folder one level up from the current folder

Javascript: Event properties get lost on replacing an element

I am trying to create a todoList app with vanilla js. So far i have finished markup, design and as well as added other functionalities such as adding submitted task to ui, deleting task, marking the task as completed.
Now i am stuck at adding the edit functionality.(And of course there are still other things to do like validation , implementing localStorage, adding reminder, making it responsive etc..)
Things to achieve:
user should be able to click the edit icon and it should change to save icon,
After edit icon is clicked, users should be able to edit the text in realtime
And finally after clicking the save icon, the text should no longer be editable and the icon should change back to edit icon
some things i tried:
On clicking the edit icon,
changed contentEditable=true for text, so we can edit it(task text) in realtime.
replaced editBtn with saveBtn(newly created element)
But after replacing the element i couldn't find a way to revert it. When i tried to access it with the eventTarget(a variable i used to store target property of an event) i didn't get anything. I also tried grabbing it with document.querySelector('.save') but that does only works as per document flow.(i meant if we clicked the 2nd button, in dom the first button gets changed)
Now i would like to have the functionality to replace the saveBtn back to the editBtn and change contentEditable back to false or inherit
Here is function which handles the ui events :
static taskEvents(eventTarget) {
const targetClassName = eventTarget.classList
if(targetClassName.contains('complete')) {
targetClassName.toggle('statusIcon');
eventTarget.parentElement.nextElementSibling.classList.toggle('task');
}
else if(targetClassName.contains('edit')) {
// let textEditableStatus = eventTarget.parentElement.parentElement.previousElementSibling;
// textEditableStatus.contentEditable=true;
// const editBtn = eventTarget.parentElement
// const saveBtn = document.createElement('a');
// saveBtn.className = "btn";
// saveBtn.id = "saveBtn";
// saveBtn.innerHTML = '<i class="fas fa-save save"></i>';
// editBtn.replaceWith(saveBtn)
}
else if(targetClassName.contains('delete')) {
eventTarget.parentElement.parentElement.parentElement.remove();
}
}
complete code:
class UI {
// dummy data; for now.
static displayTasks() {
const tasks = ['Take out trash', 'Do laundry', 'Visit part'];
tasks.forEach(task => UI.addTask(task));
}
static addTask(task) {
const tbody = document.querySelector('#tasks');
const taskRow = document.createElement('tr');
taskRow.className += 'task';
taskRow.innerHTML = `
<td><i class="far fa-check-circle complete"></i></td>
<td>${task}</td>
<td><i class="fas fa-edit edit"></i></td>
<td><i class="fas fa-trash delete"></i></td>
`;
tbody.appendChild(taskRow);
document.querySelector('#todoInput').value = '';
}
static taskEvents(eventTarget) {
const targetClassName = eventTarget.classList
if (targetClassName.contains('complete')) {
targetClassName.toggle('statusIcon');
eventTarget.parentElement.nextElementSibling.classList.toggle('task');
} else if (targetClassName.contains('edit')) {
// let textEditableStatus = eventTarget.parentElement.parentElement.previousElementSibling;
// textEditableStatus.contentEditable=true;
// const editBtn = eventTarget.parentElement
// const saveBtn = document.createElement('a');
// saveBtn.className = "btn";
// saveBtn.id = "saveBtn";
// saveBtn.innerHTML = '<i class="fas fa-save save"></i>';
// editBtn.replaceWith(saveBtn)
} else if (targetClassName.contains('delete')) {
eventTarget.parentElement.parentElement.parentElement.remove();
}
}
}
// Ui events
document.addEventListener('DOMContentLoaded', UI.displayTasks);
const tbody = document.querySelector('#tasks');
tbody.addEventListener('click', event => {
UI.taskEvents(event.target);
})
.statusIcon {
font-weight: bold;
color: rgb(48, 158, 81);
}
td.task {
opacity: .6;
text-decoration: line-through;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Todo List App</title>
<!-- Custom css -->
<link rel="stylesheet" href="style.css">
<!-- fontawesome script-->
<script src="https://kit.fontawesome.com/39350fd9df.js"></script>
</head>
<body>
<div class="main-container">
<div class="container">
<div class="input-group">
<input type="text" id="todoInput" placeholder="Enter new task...">
<i class="fas fa-plus"></i>
</div>
<table class="taskLister">
<thead>
<tr>
<th>Status</th>
<th>Task</th>
<th>Edit</th>
<th>Delete</th>
</tr>
</thead>
<tbody id="tasks"></tbody>
</table>
</div>
</div>
<!-- Custom script -->
<script src="app.js"></script>
</body>
</html>
A quick note: I am using pure javascript
Here is the fiddle: https://jsfiddle.net/Pixie_Dust/Lcnwu4ao/5/
Using innerHTML to rewrite element content will destroy the handlers defined on it. If you use event delegation, this problem will not occur, because the delegated handler uses the actual elements to determine which action is needed. Here's a minimal reproducable example for a table with one entry.
Here's a rewritten version of your jsFiddle, and here's a (somewhat extended) stackblitz version of it.
document.addEventListener("click", handle);
function handle(evt) {
const origin = evt.target;
if (origin.dataset.edit) {
const entryEdit = origin.closest("tr").querySelector("td:nth-child(2)");
entryEdit.contentEditable = true;
return entryEdit.focus();
}
if (origin.dataset.save) {
const entry = origin.closest("tr");
const value = entry.querySelector("td:nth-child(2)")
if (value.contentEditable === "true") {
value.contentEditable = false;
return entry.querySelector("td:nth-child(5)").textContent = "saved!";
};
return entry.querySelector("td:nth-child(5)").textContent = "nothing to do";
}
if (origin.dataset.setstatus) {
const row = origin.closest("tr");
const nwStatus = origin.dataset.setstatus === "Todo" ? "Done" : "Todo";
row.dataset.status = nwStatus;
origin.dataset.setstatus = nwStatus;
row.querySelectorAll("td > button")
.forEach(btn =>
btn[nwStatus === "Done"
? 'setAttribute'
: 'removeAttribute']("disabled", true));
return row.querySelector("td:nth-child(5)").textContent = "";
}
}
body {
margin: 2rem;
font: 12px/15px normal verdana, arial;
}
th {
background: black;
color: white;
text-align: left;
padding: 2px;
}
td {
padding: 2px;
}
th:nth-child(5) {
min-width: 75px;
}
th:nth-child(2) {
min-width: 200px;
}
td[data-setstatus]:after {
content: attr(data-setstatus);
}
tr[data-status='Done'] td:nth-child(2) {
text-decoration: line-through;
}
<table>
<thead>
<tr>
<th>Status</th>
<th>Task</th>
<th>edit</th>
<th>save</th>
<th>result</th>
</tr>
</thead>
<tbody>
<tr data-status="Todo">
<td data-setstatus="Todo"></td>
<td>Hi, I am a task</td>
<td><button data-edit="1">edit</button></td>
<td><button data-save="1">save</button></td>
<td></td>
</tr>
</tbody>
</table>

How do I edit or updated an array of Objects CRUD app dynamically?

I am working on a bookmark "collector" app that allows users save websites urls as a collection. I have created an array collectX in the localstorage to save each collections. However I am trying to edit and update each collections that have created on another HTML page.
How can I do that?
Here is what I have tried so far:
//get form values
// create an object of the form values
//create an empty array
//append object of the form values to the empty array
//display array object values
showCollection();
var getButton = document.getElementById('clickIt');
var collectionTitle = document.getElementById("title");
var collectionDescription = document.getElementById('describe')
getButton.addEventListener('click', function(e){
e.preventDefault()
var collections = {
title: collectionTitle.value,
description: collectionDescription.value,
collectedWebsites:[]
}
let webCollections = localStorage.getItem('collectx');
if(webCollections == null){
var collectionObj = []
alert('storage is empty')
}
else{
collectionObj = JSON.parse(webCollections);
}
collectionObj.push(collections);
localStorage.setItem("collectx", JSON.stringify(collectionObj));
showCollection()
});
function showCollection(){
let webCollections = localStorage.getItem('collectx')
if(webCollections == null){
var collectionObj = []
alert('storage is empty')
}
else{
collectionObj = JSON.parse(webCollections);
}
let html= ''
var demos = document.getElementById('demo');
collectionObj.forEach(function(item, index){
html += `<div class="collects">
Title: ${item.title} <br>
Description: ${item.description} </div>`
})
demos.innerHTML = html
}
body{
background-color: #000;
}
.collects{
width: 150px;
height: 100px;
padding: 10px 5px 10px 5px;
margin-right: 20px;
border-radius: 10px;
display: inline-block;
background-color: #fff;
}
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CollectX</title>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<form id="forms">
<input id="title" type="text" placeholder="Collection name">
<br>
<br>
<input id="describe" type="text" placeholder="Description">
<button id="clickIt"> Submit </button>
</form>
<div id="demo">
</div>
<script src="/script.js"></script>
</body>
</html>
Here is the link to the JSFiddle: https://jsfiddle.net/c3jgezwr/2/
P.S: I have tried to the method used on this page: https://www.xul.fr/javascript/parameters.php
Please take a look to this example
CSS
body {
background-color: #000;
}
.collects {
min-width: 150px;
min-height: 100px;
padding: 10px 5px 10px 5px;
margin-right: 20px;
border-radius: 10px;
display: inline-block;
background-color: #fff;
overflow: hidden;
}
HTML
<form name="form">
<div>
<input name="title" placeholder="Title" />
</div>
<div>
<input name="describe" placeholder="Describe" />
</div>
<div>
<input name="links" placeholder="Add links separated by coma" />
</div>
<div>
<button type="submit">Submit</button>
</div>
</form>
JS
const form = document.forms.form;
form.addEventListener("submit", submitHandler);
function getData() {
return JSON.parse(localStorage.getItem("collectx")) ?? [];
}
function submitHandler(event) {
event.preventDefault();
const form = event.target;
const formData = new FormData(event.target);
const data = Object.fromEntries(formData);
const currentData = getData();
localStorage.setItem("collectx", JSON.stringify([...currentData, data]));
form.reset();
render();
}
function render() {
const collection = getData();
const entries = collection
.map(
({ title, describe, links }) => `
<div class="collects">
<p>Title: ${title}</p>
<p>Describe: ${describe}</p>
<p>Links: ${links && links
.split(",")
.map((link) => `${link}`)
.join("<br />")}
</p>
</div>`
)
.join("");
document.querySelector("#root").innerHTML = `
<div>
${entries}
</div>
`;
}
render();
https://jsfiddle.net/m3ws94zo/2/
The idea is to add a input to enter links separated by coma. In a real solution, you probably will need to validate the urls

Javascript array being set to null in prototype

I am learning javascript and practicing by making a simple book list app.
I wanted to add the books to local storage. But the array I want to push the values into is starting as undefined and then it is set to null. And the if else statement is not working it runs through both the if statement despite the fact that the condition should return true. It starts on line 32 First a variable booklist is declared then it checks to see if bookLists exists in local storage if it does not it sets the value bookLists to a empty array ELSE it grabs booklist from local storage and parses the array adds the book to the book list. Then sets the item to local storage. At least that is what I was trying to do. Any ideas what I am not doing correctly? 0.0
The HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/skeleton/2.0.4/skeleton.min.css">
<link rel="stylesheet" href="css/main.css">
<style>
.correct {
color: antiquewhite;
padding: 7px;
margin: 5px 0px 16px 0px;
border: 3px forestgreen solid;
border-radius: 6px;
background-color: forestgreen;
}
.error {
color: antiquewhite;
padding: 7px;
margin: 5px 0px 16px 0px;
border: 3px firebrick solid;
border-radius: 6px;
background-color: firebrick;
}
</style>
<title>Book List Generator</title>
</head>
<body>
<div id="container" class="container booklistContainer">
<h1 class="booklistH1">Add a Book to the Booklist &:<)</h1>
<form id="form">
<div>
<label for="title">Title</label>
<input type="text" id="title" class="u-full-width">
</div>
<div>
<label for="author">Author</label>
<input type="text" id="author" class="u-full-width">
</div>
<div>
<label for="isbn">ISBN#</label>
<input type="text" id="isbn" class="u-full-width">
</div>
<div>
<button class="u-full-width" id="submit">Add a bookmark</button>
</div>
<hr>
<table class="u-full-width">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th></th>
</tr>
</thead>
<tbody id="BookListBody"></tbody>
</table>
</div>
<script src="Js/booklistGenerator.js"></script>
</body>
</html>
The Css
.booklistH1 {
letter-spacing: 1px;
margin: 3rem 0rem;
font-size: 1.5rem;
}
.booklistContainer {
margin-bottom: 7rem;
}
#media screen and (max-width: 519px) {
.booklistH1 {
letter-spacing: 1px;
margin: 3rem 0rem;
font-size: 1rem;
}
}
#media screen and (max-width: 352px) {
.booklistH1 {
font-size: 0.9rem;
}
}
#media screen and (max-width: 352px) {
.booklistH1 {
letter-spacing: 1px;
margin: 3rem 0rem;
font-size: 0.8rem;
}
}
The Javascript
// adding a event listener
const sub = document.getElementById("submit").addEventListener("click", valuerRetrivel);
const removeBook = document.getElementById("BookListBody").addEventListener("click", bookRemover);
// the book constructer
function BookConstructer (title, author, isbn){
this.title = title;
this.author = author;
this.isbn = isbn;
};
// The Ui constructer
function UiConstructor() {}
// adding a method to the Ui constructer prtotype
UiConstructor.prototype.addBookToList = function(book){
// grab the table body
const list = document.getElementById("BookListBody");
//create the table row to append the table cells
const row = document.createElement("tr");
// add the cells to the table row using templet strings
row.innerHTML = `
<td>${book.title}</td>
<td>${book.author}</td>
<td>${book.isbn}</td>
<td>X</td>
`;
// append to the table body
list.appendChild(row);
let bookList;
if (localStorage.getItem("bookList" === null)) {
bookList = [];
}else {
bookList = JSON.parse(localStorage.getItem("bookList"));
}
bookList.push(book);
localStorage.setItem("bookList", JSON.stringify(bookList));
alert("task saved");
}
UiConstructor.prototype.alertMessage = function(message, className) {
// create and append the alert message
const alertDiv = document.createElement("div");
alertDiv.className = `alert ${className}`;
alertDiv.setAttribute("id", "alert");
const alertDivTextNode = document.createTextNode(message);
alertDiv.appendChild(alertDivTextNode);
const parent = document.getElementById("container");
const form = document.getElementById("form");
parent.insertBefore(alertDiv, form);
// remove the alert after 3 seconds
setTimeout(function(){
document.getElementById("alert").remove();
},3000);
}
UiConstructor.prototype.successMessage = function(message, className) {
// create and append the success message
const successDiv = document.createElement("div");
successDiv.className = `success ${className}`;
successDiv.setAttribute("id", "success");
const successtDivTextNode = document.createTextNode(message);
successDiv.appendChild(successtDivTextNode);
const parent = document.getElementById("container");
const form = document.getElementById("form");
parent.insertBefore(successDiv, form);
console.log(UiConstructor);
// remove the alert after 3 seconds
setTimeout(function(){
document.getElementById("success").remove();
},3000);
}
// retriving the form values
function valuerRetrivel(e) {
// initating a Ui constructor to accses its methods
const ui = new UiConstructor();
// reguler expression that checks for whitespace
const regexp = /^\s+$/;
// retriving the form input values
const title = document.getElementById("title").value,
author = document.getElementById("author").value,
isbn = document.getElementById("isbn").value;
const resultTitle = regexp.test(title);
const resultAuthor = regexp.test(author)
const resultIsbn = regexp.test(isbn);
// cheacking for white space
if (resultTitle === true
|| resultAuthor === true
|| resultIsbn === true
|| title === ""
|| author === ""
|| isbn === "") {
// calling the alert message and passing the arguments
ui.alertMessage("All form fields must have content", "error");
e.preventDefault();
return false;
}else {
// calling the book constructer function to create a book object
const book = new BookConstructer(title, author, isbn);
// initating the ui constructer and creating a new book object
ui.addBookToList(book);
console.log(ui);
// calling the success message method and passing the arguments
ui.successMessage("Success!", "correct");
// clearing the current input values
const titleClear = document.getElementById("title").value = "",
authorClear = document.getElementById("author").value = "",
isbnClear = document.getElementById("isbn").value = "";
e.preventDefault();
return true;
}
};
function bookRemover(e) {
if (e.target.className === "delete") {
if(confirm("Are you sure you want to delete this link?")) {
e.target.parentElement.parentElement.remove();
e.preventDefault();
}
}
}
You have a typo
if (localStorage.getItem("bookList" === null)) {
which is always false.
This causes the bookList to never be instantiated from the true clause, and also as a result the storage item is attempted to be used, which is where the null parse comes in from
JSON.parse(localStorage.getItem("bookList"))

Categories

Resources