Javascript: Event properties get lost on replacing an element - javascript

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>

Related

How do I edit a td after I have clicked my edit button in JavaScript?

I am working on a oop project that I want to use after for my self and add to my portfolio. I'm also using it as a learning project as I'm new to oop.
In this project I want to be able to edit the added td fields that are submitted to the bottom table.
when I try to create input element and then append the input to the submitted items it just creates input boxes next to the tds.
(Update I have now got it to edit the first td item I'm just now working on updating the edit after clicking enter or adding a ok button to the app.)
This is my html css and js:
// this is the es5 way to do it with constructors and prototypes
// keyword constructor
function Keyword(sitename, keyword, keywordpost) {
this.sitename = sitename;
this.keyword = keyword;
this.keywordpost = keywordpost;
}
// UI Constructor is the things assosiated to the ui different prototypes and methods
function UI() {
}
// this is the UI prototype for the above UI constructor
UI.prototype.addKeywordToList = function(keywordNames) {
const list = document.getElementById('keyword-list');
// create tr element
const row = document.createElement('tr');
// insert cols
row.innerHTML = `
<td>${keywordNames.sitename}</td>
<td>${keywordNames.keyword}</td>
<td>${keywordNames.keywordpost}</td>
<td>X</td>
<td>edit</td>
`;
console.log(row);
console.log(list);
list.appendChild(row);
}
// start here on monday or tuesday ------------------------------------------------------
// show alert
UI.prototype.showAlert = function(message, className) {
//create div
const div = document.createElement('div');
// add class
div.className = `alert ${className}`;
// add text
div.appendChild(document.createTextNode(message));
// Get parent
const container = document.querySelector('.input-container');
const form = document.querySelector('#keyword-form');
//insert alert box/message above the form
container.insertBefore(div, form);
// Timeout after 3 secs
setTimeout(function() {
document.querySelector('.alert').remove();
}, 3000);
}
// clear fields of there input
UI.prototype.clearFields = function() {
document.getElementById('website-name').value = '';
document.getElementById('keyword-name').value = '';
document.getElementById('keyword-post').value = '';
}
//prototype to delete item
UI.prototype.deleteBook = function(target) {
if (target.className === 'delete') {
target.parentElement.parentElement.remove();
}
}
//prototype to edit book -------- working on this here updated now edits element but just need save the update
UI.prototype.editBook = function(target) {
if(target.className === 'edit'){
const firstItem = document.querySelector('td');
const input = document.createElement('input');
firstItem.innerHTML = '';
input.type = 'text';
firstItem.appendChild(input);
console.log(firstItem);
}
}
// Event Listeners
document.getElementById('keyword-form').addEventListener('submit', function(e) {
// get form values
const siteName = document.getElementById('website-name').value
const keywordName = document.getElementById('keyword-name').value
const keywordPost = document.getElementById('keyword-post').value
//instantiate a book creating a new one based of the Keyword object constructor ---------------------------
const keywordNames = new Keyword(siteName, keywordName, keywordPost);
//instantiate UI object ----------------------------------------------------
const ui = new UI();
//create validation to stpp crosses being submited on a line when there is nothing to submit
if (siteName === '' || keywordName === '' || keywordPost === '') {
ui.showAlert('please fill in all fields', 'error');
} else {
ui.addKeywordToList(keywordNames);
ui.clearFields();
ui.showAlert('Your item has been added', 'success');
}
console.log('test');
e.preventDefault();
});
//event listener for delete
document.getElementById('keyword-list').addEventListener('click', function(e) {
console.log(123);
//instatiate ui again beceuse we outside of above function
const ui = new UI();
ui.deleteBook(e.target);
ui.showAlert('Your item has been removed', 'success');
e.preventDefault();
});
//event listener for editing the table items --------- working on this here
document.getElementById('keyword-list').addEventListener('click', function(e) {
const ui = new UI();
ui.editBook(e.target);
e.preventDefault();
});
* {
margin: 0;
box-sizing: border-box;
}
.main-container {
width: 100%;
height: 100vh;
}
.input-container {
display: flex;
height: 50%;
width: 90%;
margin: auto;
flex-direction: column;
justify-content: space-around;
}
.inputContainer {
width: 100%;
display: flex;
flex-direction: column;
justify-content: space-around;
flex-wrap: wrap;
}
#keyword-form {
display: flex;
flex-direction: column;
align-content: space-between;
justify-content: space-evenly;
flex-wrap: wrap;
width: 100%;
/* padding-top: 6px; */
height: 90%;
}
.input {
padding: 10px;
}
.submit-button {
padding: 10px;
width: 120px;
}
.output-table {
width: 100%;
}
tr {
text-align: center;
}
.success,
.error {
color: white;
padding: 5px;
margin: 5px 0 15px 0;
}
.success {
background: green;
}
.error {
background: red;
}
<!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">
<link rel="stylesheet" href="style.css">
<title>Document</title>
</head>
<body>
<div class="main-container">
<div class="input-container">
<h1>Seo Keyword Tracker</h1>
<form id="keyword-form">
<div class="inputContainer">
<label for="Website-Name">Website Name</label>
<input class="input one" id="website-name" type="text" placeholder="keyword">
</div>
<div class="inputContainer">
<label for="Keyword-Name">Keyword Name</label>
<input class="input two" id="keyword-name" type="text" placeholder="keyword">
</div>
<div class="inputContainer">
<label for="Keyword-Site-Number">Keyword Post Title</label>
<input class="input three" id="keyword-post" type="text" placeholder="keyword">
</div>
<div class="inputContainer">
<input class="submit-button" id="Keyword-Site-Name" type="submit" placeholder="keyword">
</div>
</form>
</div>
<hr>
<table class="output-table">
<thead>
<tr>
<th>Website Name</th>
<th>Keyword Name</th>
<th>Keyword Post Title</th>
<th></th>
</tr>
</thead>
<tbody id="keyword-list"></tbody>
</table>
</div>
<script src="app.js"></script>
<script src="appes6.js"></script>
</body>
</html>
Ok so if you run snippet you will see the input boxes that I mentioned also when I inspect element they seem to me creating a new tr and then the inputs outputting within that.
I'm not sure were I'm going wrong i thought maybe i could grab the td by class or id but they do not have class and ids as there created dynamically.
I thought maybe grabbing the innerHTML fields used in the (add) prototype one and add that into the (edit) prototype one.
(Update i have now got it to edit the first td item I'm just now working on updating the edit after clicking enter or adding a ok button to the app.)
So if anyone could help it would be greatly appreciated based on my update this post.
Many Thanks

JavaScript DOM table manipulation

First Problem
How to modify the function cut() to apply "line-through" to all td elements not for only the first.
Second Problem
When I generate the table I don't know what I'm missing in this.details to automatically generate the th of the table only one time (not to display in html like in the code below) because I tried
this.details = `<tr>
<th>Item description<\th>
<th>Action<\th>
<td>${this.item}</td>
<td>${this.action}</td>
</tr>`;
and the th is generate for each td.
<!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="style.css">
<title>list</title>
</head>
<body>
<h2>list</h2>
<div class="container">
<input type="text" name="item" id="item">
<label for="item"></label>
<input type="button" value="Add item" class="addBtn" id="add">
</div>
<div class="container" id="sort">
<input type="button" value="Sort asc" class="btn">
<input type="button" value="Sort desc" class="btn">
</div>
<div class="tableData" id="table">
<table id="display-none">
<tr>
<th class="show-succes">product</th>
<th class="show-succes">mark</th>
</tr>
</div>
<script src="app.js"></script>
</body>
</html>
function Item(item, action, table) {
this.item = item;
this.action = `<input type="button" value="Mark as buyed" class="newBtn" id="buton" onclick="cut()" `;
this.details = `<tr>
<td>${this.item}</td>
<td>${this.action}</td>
</tr>`;
this.table = table;
this.addToTable = function () {
this.table.innerHTML += this.details;
};
}
const addBtn = document.getElementById('add');
addBtn.addEventListener('click', addNewItem);
function addNewItem() {
const items = document.getElementById('item').value;
const actions = 'mark as buyed'
const myTable = document.getElementById('display-none');
const item = new Item(items, actions, myTable);
item.addToTable();
}
function cut() {
let el = document.querySelector("td");
el.style.textDecoration = "line-through";
}
*{
margin: 0px;
padding: 0px;
box-sizing: border-box;
text-decoration: none;
}
h2 {
text-align: center;
padding: 60px ;
}
input[type="text"]{
margin-right: 20px;
}
label{
padding: 15px;
}
.btn{
padding: 5px;
margin-top: 20px;
margin-right: 10px;
}
#sort{
margin-left: -90px;
}
.container{
display: flex;
justify-content: center;
}
#table{
width: 40%;
text-align: center;
margin-left: 650px;
margin-top: 20px;
}
Your approach is much more involved than necessary and really wouldn't do you any good to try to fix it.
See comments inline below for the most simple approach.
// Get reference to the elements you'll use
// over and over, just once.
const input = document.getElementById("item");
const tbl = document.querySelector("table");
const add = document.querySelector(".addBtn");
// Add an event handler for the add button click
add.addEventListener("click", function(){
let row = tbl.insertRow(); // Add a row to the table
let itemCell = row.insertCell(); // Add a td to the row
itemCell.textContent = input.value; // Put the input value in the td
let actionCell = row.insertCell(); // Add a second td to the row
let chk = document.createElement("input"); // Create a new input
chk.type = "checkbox"; // Make the input a checkbox
chk.value = "bought"; // Set a value for the checkbox
// Set up an event handler for the new checkbox
chk.addEventListener("click", function(){
// Find the nearest ancestor tr and then query it
// for the first td in it. Then toggle the use of the
// "strike" CSS class to add or remove strikethrough.
this.closest("tr").querySelector("td").classList.toggle("strike");
});
actionCell.appendChild(chk); // Add the checkbox to the td
input.value = ""; // Clear out the textbox
tbl.classList.remove("hidden"); // Show the table
});
body {
font-family:Calibri, Helvetica, Arial;
}
h1 {
font-size:1.8em;
}
div {
margin:1em;
}
.strike {
text-decoration-line: line-through;
}
.hidden {
display:none;
}
<h1>SHOPPING LIST</h1>
<div class="addItems">
<input type="text" id="item">
<input type="button" value="Add item" class="addBtn">
</div>
<table class="hidden">
<tr>
<th>Item</th>
<th>Bought?</th>
</tr>
</table>

Save todos in localStorage

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

I can't figure out why the javascript generated <tr> elements aren't being styled by my stylesheet

I'm trying to make a table with the background color alternating for every other row element. The styling seems to work for the row elements already in the html code, but when I generate some more rows using javascript, the new rows aren't styled and I can't figure out why. Any help would be appreciated.
/* class Definitions
*******************/
class ladder{
constructor(){
this.data;
}
async getLeaderBoard(){
try{
const ret = await fetch(`https://cors-anywhere.herokuapp.com/https://aoe2.net/api/leaderboard?game=aoe2de&leaderboard_id=3&start=1&count=10`);
this.data = await ret.json();
}
catch(error){
console.log(error);
}
}
}
//table DOM functions
const renderCell = player => {
const markup = `
<tr class="player-entry">
<td>${player.rank}</td>
<td>${player.rating}</td>
<td><a href= "">${player.name}</td>
<td>${player.games}</a></td>
<td>${Math.round(100*player.wins/player.games)/100}</td>
</tr>`;
document.querySelector('.leaderboards').insertAdjacentHTML('beforeend', markup);
};
const renderTable = leaderBoard =>{
leaderBoard.forEach(renderCell);
}
/***********************
/*MAIN
/***********************/
/*store data in window*/
const state = {};
/*
* Fetch the data and save in state array
*/
const dataFetcher = async () =>{
//store the ladder object in state array
state.AOELadder = new ladder();
//load ladder from api
await state.AOELadder.getLeaderBoard();
//update to UI
renderTable(state.AOELadder.data.leaderboard);
}
dataFetcher();
.leaderboards{
width:80%;
text-align: left;
margin-left:3rem;
margin-top:3rem;
line-height: 1.4rem;
border:#888888 solid 1px;
border-collapse: collapse;
}
.leaderboards .title{
border-bottom:#888888 solid 1px;
background:#222222;
color:#fff;
height:2rem;
}
.leaderboards a{
text-decoration: none;
color:#fff;
}
.leaderboards .player-entry{
background: #999999;
border-bottom:#888888 solid 1px;
color:#fff;
}
.leaderboards .player-entry:nth-child(odd){
background: #222222;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title> Practice Fetching API Data </title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<table class="leaderboards">
<tr class="title">
<th>Rank</th>
<th>Rating</th>
<th>Name</th>
<th>Games</th>
<th>Win Rate</th>
</tr>
<tr class="player-entry">
<td>565</td>
<td>1500</td>
<td>dummy-entry</td>
<td>1000</td>
<td>55%</td>
</tr>
<tr class="player-entry">
<td>565</td>
<td>1500</td>
<td>dummy-entry</td>
<td>1000</td>
<td>55%</td>
</tr>
<tr class="player-entry">
<td>565</td>
<td>1500</td>
<td>dummy-entry</td>
<td>1000</td>
<td>55%</td>
</tr>
</table>
<script type="text/javascript" src= "./main.js"></script>
</body>
</html>
The table tag automatically appends tbody element (TABLE must have one or more TBODY elements) to wrap the rows. Therefore, you need to add the row inside the body in order to determine alternation.
Just update your inserting script to this:
document.querySelector('.leaderboards').getElementsByTagName('tbody')[0].insertAdjacentHTML('beforeend', markup);

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