I have a form with input fields whose values are to be stored in an array in the form of an object. At the same time, these values are displayed in the browser. Now I want to allow the user to delete the displayed values, i.e. the created object. Unfortunately, the function I have set up so far "deletes" very randomly (that's how it seems to me). What do I have to change so that the function deletes the element that is clicked?
HTML:
<div class="readContainer">
<label for="radio">read?</label>
<div class="checkReadContainer">
<input type="checkbox" id="read" name="read" value="yes">
</div>
</div>
<button id="btn" onclick="createBook();">submit</button>
</div>
<div class="displayMessageContainer">
<p id="displayBook">📚 store up to 10 books 📚</p>
<p id="infoTestVersion"> or sign up for our free trial and get 30 days of unlimited
storage for your books*</p>
</div>
<div class="Display">
<p class="display"> </p>
</div>
<footer>
<div class="footerContainer">
<div class="madeBy">
<h1> made by anitakath <i class="fa-brands fa-github"></i> </h1>
</div>
</div>
</footer>
JavaScript:
const labels = document.querySelectorAll("label");
const inputs = document.querySelectorAll("input");
const title = document.getElementById("title");
const author = document.getElementById("author");
const pages = document.getElementById("pages");
const button = document.getElementById("btn");
const displayArea = document.querySelector(".display");
let myLibrary = [];
function Book(title, author, pages) {
this.title = title.value;
this.author = author.value;
this.pages = pages.value;
}
Book.prototype.introduction = function () {
return `${this.title} by ${this.author}, `;
};
Book.prototype.introductionII = function () {
return `with ${this.pages} pages`;
};
let errorMessage = document.getElementById("displayBook");
function createBook() {
if (title.value && author.value && pages.value) {
let bookX = new Book(title, author, pages);
let card = document.createElement("div");
card.classList.add("cardStyle");
displayArea.appendChild(card);
let cardSectionLeft = document.createElement("div");
let cardSectionRight = document.createElement("div");
cardSectionLeft.classList.add("cardLeft");
cardSectionRight.classList.add("cardRight");
card.appendChild(cardSectionLeft);
card.appendChild(cardSectionRight);
let deleteButton = document.createElement("div");
let deleteContent = document.createElement("p");
deleteContent.innerText = "X";
deleteButton.classList.add("deleteButton");
deleteButton.appendChild(deleteContent);
cardSectionRight.appendChild(deleteButton);
deleteButton.setAttribute("onclick", "deleteBook();");
let cardIntro = document.createElement("h3");
let cardOutro = document.createElement("p");
cardIntro.innerText = bookX.introduction();
cardOutro.innerText = bookX.introductionII();
cardSectionLeft.appendChild(cardIntro);
cardSectionRight.appendChild(cardOutro);
bookArray = [title.value, author.value, pages.value];
const bookArrayToObject = Object.assign({}, bookArray);
console.log(bookArrayToObject);
myLibrary.push(bookArrayToObject);
console.log(myLibrary);
console.log(myLibrary[0]);
console.log(myLibrary[1]);
console.log(myLibrary[2]);
const radio = document.getElementById("read");
if (radio.checked) {
card.classList.add("read");
} else if (!radio.checked) {
card.classList.add("notread");
}
errorMessage.innerText = `saved: ${this.title.value} by ${this.author.value}, with ${this.pages.value} pages`;
errorMessage.style.color = "green";
} else if (!title.value || !author.value || !pages.value) {
errorMessage.innerText =
"please make sure you have filled in all input fields";
errorMessage.style.color = "red";
}
}
function deleteBook() {
for (let i = 0; i < myLibrary.length; i++) {
console.log(myLibrary.splice(i, 1));
}
}
The splice method on JavaScript arrays mutates the array in place. This means that, once it's run (even if you're just logging the result, etc), it's already actually changed the array.
Let's take a closer look at what it does, from the MDN page:
The splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements in place.
start: Zero-based index at which to start changing the array
deleteCount: An integer indicating the number of elements in the array to remove from start.
Returns: An array containing the deleted elements.
I believe you're probably expecting that return value to be the new array, with just the element you want deleted, and that's why the result looks like it's deleting a "random" element as you put it.
So, lets look at how to delete correctly, either you can take in the index of the book, or you can take in the book itself and delete it (I think that's the better approach, but you'll have to do a little refactoring to get there).
function deleteBookByIndex(bookIndex) {
myLibrary.splice(bookIndex, 1);
}
function deleteBook(book) {
const bookIndex = myLibrary.indexOf(book);
myLibrary.splice(bookIndex, 1);
}
You will, of course, also need to think about how updates to your internal data store are reflected in the HTML, e.g. to remove the element. Ideally maybe your Book class keeps some reference to the DOM elements that you create, so you can directly update them, as that would be cleaner than having to go searching through the DOM to find them again later.
A few notes on your code as it stands:
deleteButton.setAttribute("onclick", "deleteBook();")
Using the onclick etc attributes on HTML is very much not the recommended way to add event listeners anymore, as it only allows for one, and you have to write JavaScript code as a string, which is problematic for many reasons --- especially in a case like this one, where we'll want to pass an argument in. Instead, lets do it like so:
deleteButton.addEventListener("click", () => deleteBook());
You seem to create both an class instance (let bookX = new Book(title, author, pages);), an array (bookArray = [title.value, author.value, pages.value]) and an object (Object.assign({}, bookArray)) to represent your book. My advice would be to pick one representation and stick with it.
Use ES6 classes rather than the prototype approach you have here at the moment if you choose to stick with using the Book class.
Related
In the script below, I want to be able to display on the main html page lists of paragraphs saved in the localstorage. In the html I defined an are with the id "content". I want to display the texte stored in the localstorage in this area.
In the script below the function "displaylocalstorage" does not allow me to display the values that have been saved in the localstorage with the function "storedparagraphs". Can you please give me some guidelines to correct the "displaylocalstorage" function? Is my while loop correct ? Is the way I call the fucntion "display locastorage" is correct ?
Here is the html and js script below:
Javascript:
const mybutton = document.getElementById ("addbutton");
const mytext = document.getElementById("mytext");
const content = document.getElementById("content");
function displaylocalstorage() {
let n = 0;
while (localStorage.getItem("content" + n)) {
n++;
}
while (n){
const paragraph = document.createElement("p");
paragraph.innerText = localStorage.getItem("content");
content.appendChild(paragraph);
n++
}
}
}
displaylocalstorage()
displaylocalstorage is not being called.
add this to your js
const buttonshow = document.getElementById("buttonshow");
buttonshow.addEventListener("click", displaylocalstorage);
and to your html:
<input
type="button"
value="show"
id="buttonshow"
class="buttonshowall"
/>
and console log items in the displaylocalstorage
Thank would be a good start. Other than this in that paragraph remove length from n as n is a number. If you keep it as length it will error.
if(n>0){
let lastposition = n -1;
localStorage.removeItem("content", lastposition)
}
Another big one is change const n to let as you try to update n and const won't allow you to do that.
I'm trying to create a function which takes an object with a few parameters and returns a newly created div.
From what i can see, there seem to be two main ways to accomplish this:
creating each element by itself and appending it
creating a template literal and set the divs innerHTML
the inputs of the functions are not user generated, so i don't think using template literals will create a security issue (please educate me if i'm wrong)
So now my questions are the following:
is one more efficient than the other?
is one preferred?
are there any other concerns?
is there an even more efficient/better way?
below you can see the two solutions i've come up with.
function createDiv (entry) {
const div = document.createElement('div')
div.classList.add('exchange')
div.id = entry.exchange
const img = document.createElement('img')
img.src = `/static/img/${entry.img}.png`
img.alt = entry.name
img.classList.add('logo-image')
div.appendChild(img)
const link = document.createElement('a')
link.href = entry.url
link.classList.add('name')
link.innerText = entry.name
div.appendChild(link)
const routing = document.createElement('span')
routing.innerText = entry.routing ? entry.routing : ''
div.appendChild(routing)
const price = document.createElement('span')
price.innerText = entry.price
price.classList.add('price')
div.appendChild(price)
return div
}
function createDiv (entry) {
const div = document.createElement('div')
div.classList.add('exchange')
div.id = entry.exchange
let text = `
<img class="logo-image" src="/static/img/${entry.img}.png" alt="${entry.name}">
<a class="exchange-name" href="${entry.url}">${entry.name}</a>
<span>${routing.innerText = entry.routing ? entry.routing : ''}</span>
<span class="price">${entry.price}</span>
`
div.innerHTML = text
return div
}
Thank you in advance!
What about doing something like the following?
const createDiv = ({ exchange, img, name, url, routing: entryRouting, price }) => {
return `
<div class="exchange" id="${exchange}">
<img class="logo-image" src="/static/img/${img}.png" alt="${name}">
<a class="exchange-name" href="${url}">${name}</a>
<span>${routing.innerText = entryRouting || ''}</span>
<span class="price">${price}</span>
</div>
`;
}
In this case you are getting the full power of the template literals and of the object destructing.
About the values, you should validate them in some way before storing in the database and sanitize the HTML before getting it back. Some sort of easy validation with regex could be enough for validation. For sanitizing you can choose one of the many libraries like the this https://www.npmjs.com/package/sanitize-html.
About performances, I wouldn't take it too seriously until you do many iterations. As far as I see it is a onetime function call. So I would go for the cleaner way: template strings. But if you are curious, the template string is the fastest. The first approach is almost 100% slower. You can check the results of the test I did over 100 iterations here https://jsbench.me/7gkw1t31rs/2.
Remember that the approach I am telling you will need an innerHTML once the createDiv function returns its value.
I have built a todo list using Vanilla Javascript and localstorage. The todo list has the following key, value:
key: todolist
value: [[\"id:0\",\"title:buy groceries\",\"done:false\"],
[\"id:1\",\"title:pick up dry cleaning\",\"done:false\"],
[\"id:2\",\"title:walk dog\",\"done:false\"]]
The values display just great on my website (only the title is displaying) but when I refresh the page, the whole object is displaying.
Before page refresh:
buy groceries
pick up dry cleaning
walk dog
After page refresh:
id:0,title:buy groceries,done:false
id:1,title:pick up dry cleaning,done:false
id:2,title:walk dog,done:false
Obviously, after a page refresh I only want the title to display on the list inside the li tag. It's a head scratcher because it only does this after a page refresh.
How do I get only the title to display after page refresh?
I'm somewhat of a newbie to Javascript and can't quite figure out how to make this happen. I've been Googling for almost two days and ready to tear my hair out!!
// set up some variables for elements on the page
const form = document.querySelector('form');
const ul = document.querySelector('ul');
const button = document.querySelector('button');
const input = document.getElementById('item');
// Fix empty array when script runs by making a conditional statement that
checks if localStorage already exists
//let itemsArray = localStorage.getItem('todolist') ?
JSON.parse(localStorage.getItem('todolist')) : [];
let todolist;
if (localStorage.getItem('todolist')) {
itemsArray = JSON.parse(localStorage.getItem('todolist'));
} else {
itemsArray = [];
}
localStorage.setItem('todolist', JSON.stringify(itemsArray));
const data = JSON.parse(localStorage.getItem('todolist'));
//alert(typeof(data));
// function that creates an li element, sets the text of the element to the
parameter, and appends the list item to the ul.
const liMaker = (text) => {
const li = document.createElement('li');
li.textContent = text;
ul.appendChild(li);
// Create a "close" button and append it to each list item
var span = document.createElement("SPAN");
var txt = document.createTextNode("🗑️");
span.className = "close";
span.appendChild(txt);
li.appendChild(span);
for (i = 0; i < close.length; i++) {
close[i].onclick = function() {
var div = this.parentElement;
div.style.display = "none";
}
}
}
// Event listener that submits the value of the input
form.addEventListener('submit', function (e) {
e.preventDefault();
var id = "id:" + itemsArray.length;
var title = "title:" + input.value;
var done = "done:" + "false";
itemsArray.push([id, title, done]);
//itemsArray.push(input.value);
localStorage.setItem('todolist', JSON.stringify(itemsArray));
liMaker(input.value);
input.value = "";
});
data.forEach(item => {
liMaker(item);
});
// clear items from todolist
button.addEventListener('click', function () {
localStorage.removeItem("todolist");
while (ul.firstChild) {
ul.removeChild(ul.firstChild);
}
itemsArray = [];
});
One thing I should note, the page refresh issue doesn't happen when I change the following:
itemsArray.push([id, title, done]);
to the following:
itemsArray.push(input.value);
The main reason you are having this problem is because your JSON is not formatted properly.
The reason you are only seeing the problem on page refresh is because at this point local storage contains the "todolist" key with your improperly formed JSON. This JSON value is then stored in your data variable and output to your list items in an undesired way (as you described).
Otherwise (without page refresh) the text of your list items is coming directly from the text in the input field.
If you make the following changes to your code it will work properly (I have tested it). Hope it helps.
JavaScript comments
Firstly i'm not sure if this just happened when you posted your code here but if your comments in JS extend across two lines or more then you need to put // on all lines.
For example in your code you have:
//function that creates an li element, sets the text of the element to the
parameter, and appends the list item to the ul.
and it should be:
//function that creates an li element, sets the text of the element to the
//parameter, and appends the list item to the ul.
The format of your JSON
Secondly I see a problem with the way the JSON is formatted.
It should look something like the following (before slashes are added).
[{"id":0,"title":"buy groceries","done":false},
{"id":1,"title":"pick up dry cleaning","done":false},
{"id":2,"title":"walk dog","done":false}]
Note each property name (i.e "id", "title" and "done") should have double quotes and each property value (e.g "buy groceries") should have double quotes (unless its an int or a boolean etc).
You can use a tool called JSLint to check your JSON is valid.
So in order to create your JSON in the right format (when the form is submitted)
change these lines of code:
var id = "id:" + itemsArray.length;
var title = "title:" + input.value;
var done = "done:" + "false";
itemsArray.push([id, title, done]);
to the following:
var idValue = itemsArray.length;
var titleValue = input.value;
var doneValue = false;
itemsArray.push({"id": idValue, "title": titleValue, "done" : doneValue});
Iterating through the array
Your data variable will contain the array of todolist objects (from local storage).
So therefore the item you have in the following code will contain the full object i.e {"id":0,"title":"buy groceries","done":false}.
So in order to get the title here you need to say item.title. (This will work now that the JSON will be properly formatted):
data.forEach(item => {
//log the item to check it.
console.log(item);
liMaker(item.title);
});
I have an object declared, and I have an html form with some matching fields.
All fields in the form are in the object, but the object also has a couple of additional variables and functions.
I'd like to fill the object with the data entered in the form, what I'm trying right now overwrites the declared object, and so doesn't have the functions nor the other variables.
The object :
var Container = {
nodes: [],
Contains: function (Node) {
for (var i = 0; i < this.nodes.length; i++) {
if (this.nodes[i].nodeID === Node.nodeID)
return (i);
}
return (-1);
}
How I fill it from the form :
const handleContainerForm = event => {
event.preventDefault();
ContainerFormToJSON(form.elements);
var i = JSONData.Contains(Container);
if (i === -1)
JSONData.containers.push(Container);
else
JSONData.container[i] = Container;
output = JSON.stringify(JSONData, null, " ");
displayContents(output);
};
The form has ID, Title, Folder, Image and Description as fields, so this last Container object doesn't have the Contains() function nor the nodes[] array.
How do I end up with a complete, filled version of the object I have declared ?
In ContainerFormToJSON function, before the statement
return Container
define:
//container.nodes and container.contains
You are right, JavaScript is very different from C#, especially in regards to OOP. But that doesn't make it better or worse.
In JavaScript, you don't need to declare an object's properties, like you have to when you use classes. I think that you only want to serialize the form's input values to JSON. I recommend not to use an object that additionally has a nodes property and a Contains method.
If you need to keep a copy of the unserialized object, create two objects:
class Container {
constructor () {
this.nodes = [];
}
indexOf (node) {
return this.nodes.findIndex(n => n.nodeID === node.nodeID);
}
}
Container.nodeID = 0; // think of it as a static property
function extractValues (elements) {
// 'elements' is an array of <input> elements
// the 'container' will be filled and serialized
var container = new Container();
for (var index in elements) {
var element = elements[index];
container[element.name] = element.value;
}
container.nodeID = Container.nodeID++; // give the container a unique ID
return container;
}
var inputs = document.querySelectorAll('input');
var jsonData = new Container();
document.querySelector('button').addEventListener('click', function () {
var newContainer = extractValues(inputs);
var index = jsonData.indexOf(newContainer);
if (index === -1) {
jsonData.nodes.push(newContainer);
} else {
jsonData.nodes[index] = newContainer;
}
var jsonString = JSON.stringify(jsonData, null, ' ');
console.log(jsonString);
});
<input name="containerID">
<input name="containerTitle">
<!-- ... -->
<button>Serialize</button>
Please note: only setting an object's properties doesn't make it to JSON. It's only JSON if it's serialized to a string. I recommend this article. To serialize a JavaScript object, use JSON.stringify.
Edit:
Looking at the edit of your question, I think it might be preferable to create a Container class. Both jsonData and the containers of the form data will be instances of that class. It can contain other containers (nodes), and can get the index of such a nested container using the indexOf method. I implemented this in the above code snippet. Whenever you hit the "Serialize" button, a new container with the current <input>s' contents will be added to jsonData. The JSON form of jsonData will be logged to the console.
I hope this is what you are looking for. To better understand JavaScript OOP,
take a look at some of the articles at MDN.
So I'm making an app that, in a nut shell, takes dimensions from the user and spits out the surface area but it's quickly becoming really repetitive.
function calc() {
var dim1main = +document.getElementById("dim1main").value || 0;
var dim2main = +document.getElementById("dim2main").value || 0;
var mainSurA = dim1main * dim2main;
var dim1mainD1 = +document.getElementById("dim1mainD1").value || 0;
var dim2mainD1 = +document.getElementById("dim2mainD1").value || 0;
var mainD1SurA = dim1mainD1 * dim2mainD1;
// ...
var dim1mainD6 = ...
// ...
var totalSurA = mainSurA + mainD1SurA ... + mainD6SurA;
}
So the idea was to have hundreds of text inputs hidden until the user wanted them and everything that was left empty would run through as zero, therefor not messing with the total. I think I'm safe in assuming this is horrible javascript.
I've been looking for a way to run a function multiple times and store each local variable somewhere for later use. I've played with arrays by deleting the input values onClick but each time I run the function with .push it replaces the first value with the second. I've read about localStorage but I'm not sure that's what I'm looking for. Any suggestions? Sorry if this is too vague.
I have read about storing data in objects as well as global variables but I've heard that gets messy.
One way you can do this is instead of hiding many elements you can dynamically create new input elements when needed. You can give your elements a specific class which you can use to get it via a HTMLCollection and compute the total dimension.
For example:
document.getElementById('add').onclick = function()
{
var container = document.getElementById('container');
var subContainer = document.createElement("div");
var dim1 = document.createElement("input");
var dim2 = document.createElement("input");
dim1.className = "dim1";
dim2.className = "dim2";
dim1.value = dim2.value = 0;
subContainer.className = "sub-container";
subContainer.appendChild(dim1);
subContainer.appendChild(dim2);
container.appendChild(subContainer);
}
document.getElementById('calc').onclick = function()
{
var arrayDim1 = document.getElementsByClassName('dim1');
var arrayDim2 = document.getElementsByClassName('dim2');
var totalSurA = 0;
for (var i = 0; i < arrayDim1.length; i++) {
totalSurA += +arrayDim1[i].value * +arrayDim2[i].value;
}
console.log(totalSurA);
}
<div id="container"></div>
<button id="add">Add Dimension</button>
<button id="calc">Calculate</button>
For storing past page sessions you can use localStorage as you said. I would in JavaScript as a 2D array of each row item (or you can use an array of objects with a property for dim1/dim2), like so:
[
[dim1_0, dim2_0],
[dim1_1, dim2_1],
...
]
Although before saving it to local storage you need it in a text format, which you can convert it to using JSON.stringify()