How to add multiple element using appendChild to a parent element? - javascript

I am practising DOM manipulation using native JS my issue right now is I used a table and I am having a problem in appending my multiple columns to the table row "". Here's what I did but it will only add the first column I created.
let form = document.querySelector('#form-task');
let taskTable = document.querySelector('#tasklist-table tbody');
let addTask = (e) => {
e.preventDefault();
let task_description = document.querySelector('#input-task-description').value;
let task_notes = document.querySelector('#input-task-notes').value;
let row = document.createElement('tr');
// task description
let col1 = document.createElement('td');
col1.appendChild(document.createTextNode(task_description));
// task note
let col2 = document.createElement('td');
col2.appendChild(document.createTextNode(task_notes));
// delete button
let col3 = document.createElement('td');
// create button delete
let btn_del = document.createElement('button');
btn_del.className = 'button alert tiny center-btn"';
btn_del.appendChild(document.createTextNode('x'));
// append button to columnd 3
col3.appendChild(btn_del);
row.appendChild(col1, col2, col3); // IT IS NOT WORKING, HOW CAN I MERGE MY CREATED COLUMN THEN PASS TO THE ROW?
// append row to table
taskTable.appendChild(row);
}

appendChild only accepts a single child as an argument to be appended.
While you could call row.appendChild multiple times with the 3 <td>s, it might be easier to create the td and append it to the tr at the same time, and to use textContent instead of createTextNode:
const taskDescription = document.querySelector('#input-task-description').value;
const taskNotes = document.querySelector('#input-task-notes').value;
const row = taskTable.appendChild(document.createElement('tr'));
row.appendChild(document.createElement('td')).textContent = taskDescription;
row.appendChild(document.createElement('td')).textContent = taskNotes;
const delTd = row.appendChild(document.createElement('td'));
const delBtn = delTd.appendChild(document.createElement('button'));
delBtn.className = 'button alert tiny center-btn"';
delBtn.textContent = 'x';

Please do append child separately.
row.appendChild(col1);
row.appendChild(col2);
row.appendChild(col3);

To get something close to your original attempt, you can just put the elements to be appended in an array, and iterate that array.
[col1, col2, col3].forEach(col => row.appendChild(col));
That's not too far off from where you wanted to be.
However, I'm inclined toward making a function that receives an object structure representing the DOM structure you're creating.
let addTask = (e) => {
// e.preventDefault();
const taskTable = document.querySelector("#task-table");
const task_description = document.querySelector('#input-task-description').value;
const task_notes = document.querySelector('#input-task-notes').value;
create({
name: "tr",
children: [
{name: "td", textContent: task_description},
{name: "td", textContent: task_notes},
{name: "td", children: [
{name: "button", className: "button alert tiny center-btn", textContent: "x"},
]},
],
}, taskTable);
}
addTask();
addTask();
addTask();
console.log(document.querySelector("#task-table").outerHTML);
function create(obj, parent=document.createDocumentFragment()) {
if (!obj) return parent;
if (typeof obj === "string") {
parent.appendChild(document.createTextNode(obj));
return parent;
}
if (Array.isArray(obj)) {
obj.forEach(o => create(o, parent));
return parent;
}
const {name, children, ...props} = obj;
if (!name || typeof name !== "string") {
return parent; // or throw exception
}
const el = parent.appendChild(document.createElement(name));
for (const [key, val] of Object.entries(props || {})) {
el[key] = val;
}
return create(children, el);
}
<input id=input-task-description value=foo>
<input id=input-task-notes value=bar>
<table id=task-table>
</table>
This is just an example implementation, but you can create your DOM structures in a very concise and clear way like this.

Related

Sytax error - Even though I have already found the bug but the error still there

I am creating an app that finds the difference and checks for spelling. I am getting the same error after adding the missing curly bracket for the 'for' loop. I am not sure why it is repeating and the error is not going away.
Uncaught SyntaxError: Unexpected end of input
Please let me know if I am doing something wrong.
Thank you!`
const form = document.getElementById('form');
form.addEventListener('submit', (event) => {
// Prevent the default form submission behavior
event.preventDefault();
// Get the original text and the copy from the form
const originalText = form.elements.originalText.value.trim();
const copy = form.elements.copy.value.trim();
// Compare the original text and the copy
if (originalText === copy) {
alert('The texts are the same!');
} else {
// Display the differences between the two texts
const differencesDiv = document.getElementById('result');
differencesDiv.innerHTML = '';
// Split the texts into arrays of sentences
const originalSentences = originalText.split('. ');
const copySentences = copy.split('. ');
// Create a table element
const table = document.createElement('table');
table.classList.add('differences-table');
// Create a row for the titles
const titlesRow = document.createElement('tr');
const originalTitleCell = document.createElement('th');
originalTitleCell.innerText = 'Original';
const copyTitleCell = document.createElement('th');
copyTitleCell.innerText = 'New Version';
// Append the title cells to the titles row
titlesRow.appendChild(originalTitleCell);
titlesRow.appendChild(copyTitleCell);
// Append the titles row to the table
table.appendChild(titlesRow);
// Compare the sentences in the texts
for (let i = 0; i < originalSentences.length; i++) {
// Create a row for the sentence
const row = document.createElement('tr');
// Create cells for the original and copy sentences
const originalCell = document.createElement('td');
originalCell.innerHTML = originalSentences[i];
const copyCell = document.createElement('td');
copyCell.innerHTML = copySentences[i];
// Set the API endpoint and your API key
const apiEndpoint = 'https://api.webspellchecker.net/v2/spell-check-text';
const apiKey = 'MY API KEY';
// Check the spelling of the words in the original sentence
fetch(apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Api-Key': apiKey
},
body: `text=${encodeURIComponent(originalSentences[i])}`
})
.then(response => response.json())
.then(data => {
// data.result.spellCheck.errors contains an array of spelling errors in
// the original sentence
data.result.spellCheck.errors.forEach(error => {
// Add the span element with the different class to the original cell
originalCell.innerHTML = originalCell.innerHTML.replace(error.word, `<span class="different">${error.word}</span>`);
});
});
// Append the cells to the row
row.appendChild(originalCell);
row.appendChild(copyCell);
// Append the row to the table
table.appendChild(row);
} // Closing curly bracket for the for loop
There are several issues with your code:
Your else condition has no closing curly bracket ('}')
It is missing a closing parenthesis and curly bracket for the event listener
Corrected code:
const form = document.getElementById('form');
form.addEventListener('submit', (event) => {
// Prevent the default form submission behavior
event.preventDefault();
// Get the original text and the copy from the form
const originalText = form.elements.originalText.value.trim();
const copy = form.elements.copy.value.trim();
// Compare the original text and the copy
if (originalText === copy) {
alert('The texts are the same!');
} else {
// Display the differences between the two texts
const differencesDiv = document.getElementById('result');
differencesDiv.innerHTML = '';
// Split the texts into arrays of sentences
const originalSentences = originalText.split('. ');
const copySentences = copy.split('. ');
// Create a table element
const table = document.createElement('table');
table.classList.add('differences-table');
// Create a row for the titles
const titlesRow = document.createElement('tr');
const originalTitleCell = document.createElement('th');
originalTitleCell.innerText = 'Original';
const copyTitleCell = document.createElement('th');
copyTitleCell.innerText = 'New Version';
// Append the title cells to the titles row
titlesRow.appendChild(originalTitleCell);
titlesRow.appendChild(copyTitleCell);
// Append the titles row to the table
table.appendChild(titlesRow);
// Compare the sentences in the texts
for (let i = 0; i < originalSentences.length; i++) {
// Create a row for the sentence
const row = document.createElement('tr');
// Create cells for the original and copy sentences
const originalCell = document.createElement('td');
originalCell.innerHTML = originalSentences[i];
const copyCell = document.createElement('td');
copyCell.innerHTML = copySentences[i];
// Set the API endpoint and your API key
const apiEndpoint = 'https://api.webspellchecker.net/v2/spell-check-text';
const apiKey = 'MY API KEY';
// Check the spelling of the words in the original sentence
fetch(apiEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Api-Key': apiKey
},
body: `text=${encodeURIComponent(originalSentences[i])}`
})
.then(response => response.json())
.then(data => {
// data.result.spellCheck.errors contains an array of spelling errors in
// the original sentence
data.result.spellCheck.errors.forEach(error => {
// Add the span element with the different class to the original cell
originalCell.innerHTML = originalCell.innerHTML.replace(error.word, `<span class="different">${error.word}</span>`);
});
});
// Append the cells to the row
row.appendChild(originalCell);
row.appendChild(copyCell);
// Append the row to the table
table.appendChild(row);
}
}// Closing curly bracket for the for loop
})

How to avoid getting duplicated elements on localstorage array

i have the following code that generates a new element on an array in my localstorage each time i click on a button
let addCartItemButtons = document.getElementsByClassName('product-description-add')
for (let i = 0; i < addCartItemButtons.length; i++){
let button = addCartItemButtons[i]
button.addEventListener('click', function(event){
let buttonClicked = event.target
let getTitle = buttonClicked.parentElement.parentElement.parentElement.querySelector('.product-title').innerText
let getImage = buttonClicked.parentElement.parentElement.parentElement.querySelector('.product-header img').src
let getColor = buttonClicked.parentElement.parentElement.querySelector('.product-description-text li span').innerText
let getSize = buttonClicked.parentElement.parentElement.querySelector('.product-description-text li select').value
let getPrice = buttonClicked.parentElement.parentElement.querySelector('.product-description-price').innerText
let getSpan = buttonClicked.parentElement.parentElement.querySelector('li span').getAttribute('id')
let oldItems = JSON.parse(localStorage.getItem('newProduct')) || [];
let newItem = {
'id': i+1,
'title': getTitle,
'image': getImage,
'color': getColor,
'size': getSize,
'price': getPrice,
'spanid': getSpan,
};
let data = JSON.parse(localStorage.getItem('newProduct'))
if(localStorage.getItem('newProduct') == null) {
oldItems.push(newItem);
localStorage.setItem('newProduct', JSON.stringify(oldItems));
} else {
if(data.indexOf(newItem) == -1){
oldItems.push(newItem);
localStorage.setItem('newProduct', JSON.stringify(oldItems));
}else{
alert('element already added')
}
}
})
}
My problem is that I've tried to create a validation to check if the element has already been added to the local storage, so in that case there should appear the alert, but it's not working at all and I'm still being able to add an element to my localstorage after I added it for the first time. I can't get of how to resolve this, any ideas? :)
Problems are arising because objects do not compare as equal just because they have the same property values.
let oldItems = JSON.parse(localStorage.getItem('newProduct')) || [];
creates oldItems as an empty array or an Array of newly created objects with property values representing previous items clicked and saved.
let data = JSON.parse(localStorage.getItem('newProduct'))
will also create an array of new objects for previous buttons clicked, if there were any, but won't contain the same objects as oldItems because JSON.parse creates new objects each time it is called.
Defining newItem also creates a new object which will not be the same object as any of the entries in oldItems or data.
To solve the problem you could add unique product codes to all items, so you can search previous data for a match based on product code alone, or write a comparison routine to check all the properties that need differentiating (such as color).
Personally I would try to get a product-id added into page data, search for it among previous items, and if product-ids match, then then the item has been been clicked before (but the customer could be ordering a different color).
Be wary of using the button loop counter as an id value, or even the image source string - it's easy to imagine ways they could go wrong or be compromised by page changes outside your control.
So thanks for the help I've received from #traktor, I was able to come up with a solution to this problem. I've added a data-prodid to each product and then pass it as 'id' to localstorage. Then I check if there's any element in my array that contains the same ID i'm clicking into and if there's any result, then an alert gets executed. Otherwise, the element gets saved.
let addCartItemButtons = document.getElementsByClassName('product-description-add')
for (let i = 0; i < addCartItemButtons.length; i++){
let button = addCartItemButtons[i]
button.addEventListener('click', function(event){
let buttonClicked = event.target
let getProdId = buttonClicked.parentElement.parentElement.parentElement.getAttribute('data-prodid')
let getTitle = buttonClicked.parentElement.parentElement.parentElement.querySelector('.product-title').innerText
let getImage = buttonClicked.parentElement.parentElement.parentElement.querySelector('.product-header img').src
let getColor = buttonClicked.parentElement.parentElement.querySelector('.product-description-text li span').innerText
let getSize = buttonClicked.parentElement.parentElement.querySelector('.product-description-text li select').value
let getPrice = buttonClicked.parentElement.parentElement.querySelector('.product-description-price').innerText
let getSpan = buttonClicked.parentElement.parentElement.querySelector('li span').getAttribute('id')
let oldItems = JSON.parse(localStorage.getItem('newProduct')) || [];
let newItem = {
'id': getProdId,
'title': getTitle,
'image': getImage,
'color': getColor,
'size': getSize,
'price': getPrice,
'spanid': getSpan,
};
if(localStorage.getItem('newProduct') == null) {
oldItems.push(newItem);
localStorage.setItem('newProduct', JSON.stringify(oldItems));
} else {
let data = JSON.parse(localStorage.getItem('newProduct'))
let idCheck = data.filter(x => x.id === newItem.id).map(x => x.foo);
let idCheckResults = idCheck.length
if(idCheckResults > 0){
alert('element already added')
}else{
oldItems.push(newItem);
localStorage.setItem('newProduct', JSON.stringify(oldItems));
}
}
let windowCartProducts = JSON.parse(window.localStorage.getItem("newProduct"));
let productsInCart = document.getElementById('cartProducts')
let productsAdded = 0
for(let i = 0; i < windowCartProducts.length; i++){
productsAdded = windowCartProducts.length
}
productsInCart.innerHTML = productsAdded + `<i class="fas fa-shopping-cart"></i>`
})
}

how to remove a specific item from a list js?

I created an algorithm for displaying the title of an element in a certain block, but I can't delete what I output.
js:
var products = document.querySelectorAll('.product-block'); //objects
var product_names = document.querySelectorAll('.product-block h5'); //headings which we should output
var array_product_names = Array.prototype.slice.call(product_names); //from NodeList to array
var favourite_elements = document.querySelector('.favourite_elements'); //block where we should output
//method of determining which button is pressed
products.forEach((product, i) => {
product.onclick = function () {
if (product.style.background != "orange") {
product.style.background = "orange";
var favourite_element = array_product_names[i].outerHTML;
favourite_elements.insertAdjacentHTML('afterbegin', favourite_element); //adding elements
}
else if (product.style.background == "orange") {
//here we should delete heading of determined object from list in block, but i don't know how
}
}
});
This is oversimplified but it shows how you can remove items from a list:
let a=[1,2,3,4,5];
let toDelete=[2,3];
a=a.filter(e=>!toDelete.includes(e))
Now,the value of a would be:
[1,4,5]
Based on that idea, you could do more abstract things such as:
Array.prototype.filterA = function(f){
return this.filter(e=>f(e));
}
And from then on you would just filter elements like this:
[{color:"orange"},{a:1},{b:{c:2}}].filterA(e=>e.color!="orange")

Creating a loop for inserting data inside the table

I have got a simple loop that puts data from a list to the table:
standings.forEach(function(item, i) {
const rowData = document.createElement("tr");
const tdData = document.createElement("td");
const tdData2 = document.createElement("td");
const tdData3 = document.createElement("td");
const tdData4 = document.createElement("td");
const tdData5 = document.createElement("td");
tbody.appendChild(rowData);
tdData.textContent = item.position;
rowData.appendChild(tdData);
tdData2.textContent = `${item.name} ${item.surname}`;
rowData.appendChild(tdData2);
tdData3.textContent = item.data2;
rowData.appendChild(tdData3);
tdData4.textContent = item.point;
rowData.appendChild(tdData4);
tdData5.textContent = item.fruits;
rowData.appendChild(tdData5);
})
Anyone knows how I can automate this process? I don't think, that my way is super efficient, so I am looking for another answer.
I typically break repeating elements down into smaller pieces, and then repeat as needed to make it easier to read. For instance, you are creating a lot of 'td' elements with text inside it, so I would start with something like:
function newElementWithText( innerText )
{
const td = document.createElement("td");
td.textContent = innerText;
return td;
}
to handle that part. If you incorporate that solely into your code, you would have:
standings.forEach(function(item, i) {
const rowData = document.createElement("tr");
rowData.appendChild(
newElementWithText(item.position) );
rowData.appendChild(
newElementWithText(`${item.name} ${item.surname}`);
rowData.appendChild( newElementWithText(item.data2);
rowData.appendChild( newElementWithText(item.point);
rowData.appendChild(
newElementWithText(item.fruits) );
tbody.appendChild(rowData);
}
This can be made more readable using an array of the data that is needed, and then mapping it into the new td element, and then finally appending it.
standings.forEach(function(item, i) {
const rowData = document.createElement("tr");
const data = [
item.position,
`${item.name} ${item.surname}`,
item.data2,
item.point,
item.fruits
];
//Creates an array of the 'td' elements with text.
var tdElements = data.map( x => newElementWithText(x) );
//Iterates over the new array and appends to the rowData.
tdElements.map( x => rowData.appendChild(x) );
tbody.appendChild(rowData);
}
Full disclaimer, I have not fully tested this code for syntax errors, but it works in theory.

Loop through JSON data and appendChild <TR> is putting data in one row?

Background: I thought that I was going to end up with a dynamically created table that had the same number of <TR> tags as entries in the JSON data.
Result: The loop is only creating one table TR and putting all the data in one row.
Below is a snippet of the loop and table creation.
var tbl = document.createElement("table");
var tblBody = document.createElement("tbody");
var row = document.createElement("tr");
var tdname = document.createElement('td');
var tddate = document.createElement('td');
var tdassigned = document.createElement('td');
for (var i in data) {
console.log("Hello world!" + i);
tdname.appendChild(document.createTextNode(data[i].name));
tddate.appendChild(document.createTextNode(data[i].date));
tdassigned.appendChild(document.createTextNode(data[i].assigned));
row.appendChild(tdname);
row.appendChild(tddate);
row.appendChild(tdassigned);
}
tblBody.appendChild(row);
tbl.appendChild(tblBody);
document.getElementById("tasklist").appendChild(tbl);
Question: Do I need to create a unique row variable for each loop?
There were a couple of issues:
You're appending the text to the td, without clearing them. You can instead do document.createElement('td') within the loop for each of your data points.
You need a row for each iteration of data, this can also be done within the loop.
Please use let and const in place of var. Please see this post for more information.
Try something like this:
const data = [{
name: 'name1',
date: 'date1',
assigned: 'assigned1'
},
{
name: 'name2',
date: 'date2',
assigned: 'assigned2'
},
{
name: 'name3',
date: 'date3',
assigned: 'assigned3'
},
];
let tbl = document.createElement("table");
let tblBody = document.createElement("tbody");
data.forEach(d => {
let row = document.createElement("tr");
row.appendChild(document.createTextNode(d.name));
row.appendChild(document.createTextNode(d.date));
row.appendChild(document.createTextNode(d.assigned));
tblBody.appendChild(row);
});
tbl.appendChild(tblBody);
document.getElementById("tasklist").appendChild(tbl);
<div id='tasklist'></div>
Hope this helps,
If data is an array of objects, start with an array method like .map() or .forEach() (demo uses .foreach() since a new array isn't really needed to make a table).
That will iterate through the array using .insertRow() for each object. Now convert each object into an element of it's values with Object.values() or Object.keys() (demo uses Object.values() which saves a step).
Now iterate through the array of values with .forEach() or .map() and .insertCell() then .textContent the values to the cells.
let data = [{
name: 'John Doe',
date: '1/1/19',
assigned: 'A'
},
{
name: 'Jane Doe',
date: '2/1/819',
assigned: 'B'
},
{
name: 'Darth Vader',
date: '3/11/19',
assigned: 'C'
},
{
name: 'Obi Wan',
date: '4/20/19',
assigned: 'D'
}
];
const frag = document.createDocumentFragment();
const tbl = document.createElement("table");
const tblB = document.createElement("tbody");
tbl.appendChild(tblB);
frag.appendChild(tbl);
data.forEach((obj, idx) => {
let row = tblB.insertRow();
Object.values(obj).forEach(val => {
let cell = row.insertCell();
cell.textContent = val;
});
});
document.body.appendChild(frag);
I just saw your comment that you prefer vanilla js. Here's the jquery way if interested.
var data = [
{ Column1: "col1row1", Column2: "col2row1" },
{ Column1: "col1row2", Column2: "col2row2" }
];
$("#tasklist")
.append($('<table>')
.append($('<tbody>')));
var tbody = $("#tasklist > table > tbody");
$.each(data, function (idx, item) {
$(tbody).append($('<tr>')
.append($('<td>')
.text(item.Column1)
)
.append($('<td>')
.text(item.Column2)
)
);
});

Categories

Resources