Duplicate of Product in Shopping Cart - javascript

I have a simple problem in my shopping cart function. After i clicked the add to cart button, if it has the same product ID, it outputs a new product in a new row. It should just increase the product's quantity if it has the same product ID.
const products = [];
const carts = [];
const inputs = {
id: document.getElementById("productID"),
desc: document.getElementById("product_desc"),
qty: document.getElementById("quantity"),
price: document.getElementById("price")
};
const productsTable = document.getElementById("products-table");
const cartsTable = document.getElementById("carts-table");
function renderProductsTable() {
// delete all entries
Array.from(productsTable.children).slice(1).forEach(entry => productsTable.removeChild(entry));
for (product of products) {
const tr = document.createElement('tr');
const id = document.createElement('td');
id.textContent = product.id;
const desc = document.createElement('td');
desc.textContent = product.desc;
const qty = document.createElement('td');
qty.textContent = product.qty;
const price = document.createElement('td');
price.textContent = product.price;
const action = document.createElement('td');
const deleteButton = document.createElement('button');
deleteButton.textContent = 'Delete';
deleteButton.addEventListener('click', () => removeProduct(product.id))
const addToCartButton = document.createElement('button');
addToCartButton.textContent = 'Add to cart';
addToCartButton.addEventListener('click', () => addCart(product.id));
action.appendChild(deleteButton);
action.appendChild(addToCartButton);
tr.appendChild(id);
tr.appendChild(desc);
tr.appendChild(qty);
tr.appendChild(price);
tr.appendChild(action);
productsTable.appendChild(tr);
}
}
function addProduct() {
const product = {
id: inputs.id.value,
desc: inputs.desc.value,
qty: Number(inputs.qty.value),
price: Number(inputs.price.value)
};
let existing = products.find(item => item.id === product.id);
if (existing) {
existing.qty += product.qty;
}
else {
products.push(product);
}
renderProductsTable();
document.getElementById('order').reset();
}
function removeProduct(product_id) {
const index = products.findIndex(p => p.id === product_id);
products.splice(index, 1);
renderProductsTable();
}
function addCart(product_id) {
const product = products.find(p => p.id === product_id);
const cartItem = carts.find(c => c.product === product);
if(cartItem) {
cartItem.qty ++;
}
else {
carts.push(product);
}
renderCartTable();
}
function renderCartTable() {
for (cart of carts){
const tr = document.createElement('tr');
const id = document.createElement('td');
id.textContent = cart.id;
const desc = document.createElement('td');
desc.textContent = cart.desc;
const qty = document.createElement('td');
qty.textContent = cart.qty;
const price = document.createElement('td');
price.textContent = cart.price;
const total = document.createElement('td');
total.textContent = cart.qty * cart.price
const action = document.createElement('td');
const subtractButton = document.createElement('button');
subtractButton.textContent = 'Subtract Quantity';
const addButton = document.createElement('button');
addButton.textContent = 'Add Quantity';
const removeButton = document.createElement('button');
removeButton.textContent = 'Remove Item';
tr.appendChild(id);
tr.appendChild(desc);
tr.appendChild(qty);
tr.appendChild(price);
tr.appendChild(total);
tr.appendChild(action);
cartsTable.appendChild(tr);
}
}
<!DOCTYPE html>
<html>
<head>
<title>Shopping Cart ES6</title>
</head>
<body>
<form name="order" id="order">
<table>
<tr>
<td>
<label for="productID">Product ID:</label>
</td>
<td>
<input id="productID" name="product" type="text" size="28" required/>
</td>
</tr>
<tr>
<td>
<label for="product">Product Desc:</label>
</td>
<td>
<input id="product_desc" name="product" type="text" size="28" required/>
</td>
</tr>
<tr>
<td>
<label for="quantity">Quantity:</label>
</td>
<td>
<input id="quantity" name="quantity" width="196px" required/>
</td>
</tr>
<tr>
<td>
<label for="price">Price:</label>
</td>
<td>
<input id="price" name="price" size="28" required/>
</td>
</tr>
</table>
<input type="reset" class="resetbtn" value="Reset" />
<input type="button" id="btnAddProduct" onclick="addProduct();" value="Add New Product" >
</form>
<table border="1|1" id="products-table">
<tr>
<th>Product ID</th>
<th>Product Description</th>
<th>Quantity</th>
<th>Price</th>
<th>Action</th>
</tr>
</table>
<br />
<h2>Shopping Cart</h2>
<table border="1|1" id="carts-table">
<tr>
<th>Product ID</th>
<th>Product Description</th>
<th>Quantity</th>
<th>Price</th>
<th>Total Amount</th>
<th>Action</th>
</tr>
</table>
</body>
<script src="script.js">
</script>
</html>

So currently your code is setup that you remove all the products when adding them to the product table, however you do not do the same when adding them to the cart. So just adding this would remove everything from your cart table
Array.from(cartsTable.children).slice(1).forEach(entry => cartsTable.removeChild(entry));
There are however some minor problems with your current code, nl:
If you add twice the same product id, you don't validate if the price or the description is the same
If you add products to the carts table, you only increase the quantity by 1, however, the product itself may have a higher quantity set, so you can fix it like this
function addCart(product_id) {
const product = products.find(p => p.id === product_id);
const cartItem = carts.find(c => c.product === product);
if(cartItem) {
cartItem.qty += product.qty;
}
else {
carts.push(product);
}
renderCartTable();
}
You could use <input type="number" /> for the product quantity
You could use <input type="number" step="0.01" /> for the price field
Upon removing an item from the products table, the product should no longer be available in the cart, so you should add a call to remove the product from the cart as well
You have 2 functions that create a table, both could be generalized to share the same functionality
I have rewritten the function you are describing a bit, using an answer I had already given on another question which will create a table based on given columns and an array containing the data
It still has the problem that it won't validate differences in the description / price when adding the same product, but it helps for all other problems I have mentioned.
It might be a bit longer code, however, that is partly due to the table helper function which can do quite some things and might be overkill for the scenario you describe. It does however work, and made it a lot easier to integrate the table creation ;)
// will reassign when items get removed
let products = [];
// will reassign when items get removed
let cart = [];
function addOrIncrease(item, targetContainer, equality = (i) => i.id) {
let match = targetContainer.find(i => equality(item) === equality(i));
if (match) {
// this could actually be a problem, eg: description and price are not validated
// you might need to make sure that a warning pops up in case the price is different
match.qty += item.qty;
} else {
// didn't find so it gets added to whichever container
targetContainer.push(item);
}
}
// Gets the value of the elementId or a defaultValue
function getValue( elementId, defaultValue ) {
let elem = document.getElementById( elementId );
if (!elem || !elem.value) {
return defaultValue;
}
return elem.value;
}
// resets the value for an inputfield
function resetValue( elementId ) {
let elem = document.getElementById( elementId );
elem && (elem.value = null);
}
// adds a product to the list
function addProduct() {
let product = {
id: getValue('productId', ''),
description: getValue('productDescription', ''),
qty: parseInt(getValue('productQty', 1)),
price: parseFloat(getValue('productPrice', 0))
};
if (product.id === '') {
alert('Please enter a product id');
return;
}
addOrIncrease( product, products );
resetValue( 'productId' );
resetValue( 'productDescription' );
resetValue( 'productQty' );
resetValue( 'productPrice' );
renderProducts();
}
// adds an item to the cart
function addToCart(itemId) {
var product = products.find( p => p.id === itemId );
if (!product) {
alert('Couldn\'t find product');
return;
}
addOrIncrease( product, cart );
renderCart();
}
// removes an item from the cart
function removeFromCart(itemId) {
cart = cart.reduce( (current, item) => {
if (item.id !== itemId) {
current.push(item);
}
return current;
}, []);
renderCart();
}
// removes an item from the products list
// while simultanously removing it from the shopping cart (as it is no longer in the product list)
function removeFromProducts(itemId) {
products = products.reduce( (current, item) => {
if (item.id !== itemId) {
current.push(item);
}
return current;
}, []);
renderProducts();
// remove it from the cart, as it is no longer in the products list
removeFromCart(itemId);
}
// renders the products to the table
// will re-render the full table each time
function renderProducts() {
createTable('products', products, [{
title: 'id',
field: 'id',
class: 'left'
},
{
title: 'description',
field: 'description',
class: 'left'
},
{
title: 'quantity',
field: 'qty',
class: 'right'
},
{
title: 'price',
field: 'price',
class: 'right'
},
{
title: 'total',
value: (i) => i.price * i.qty,
class: 'right',
template: '%0 €'
},
{
title: 'action',
field: 'id',
class: 'center',
template: '<button type="button" onclick="removeFromProducts(\'%0\');">Remove product</button>' +
'<button type="button" onclick="addToCart(\'%0\');">Add to cart</button>'
}
]);
}
// renders the cart to the cart table
// will rerender each time called
function renderCart() {
createTable('cart', cart, [{
title: 'id',
field: 'id',
class: 'left'
},
{
title: 'description',
field: 'description',
class: 'left'
},
{
title: 'quantity',
field: 'qty',
class: 'right'
},
{
title: 'price',
field: 'price',
class: 'right'
},
{
title: 'total',
value: (i) => i.price * i.qty,
class: 'right',
template: '%0 €',
calculateTotal: true
},
{
title: 'action',
field: 'id',
class: 'center',
template: '<button type="button" onclick="removeFromCart(\'%0\');">Remove</button>'
}
]);
}
/* Helper function to create a table dynamically */
/* Taken from: https://stackoverflow.com/questions/43924509/creating-an-html-table-using-javascript-and-json/43925208#43925208 */
function createTable(target, data, columns) {
// gets the elements required based on id for the target div
// and creates the table, thead, tbody & tfoot for the table
let element = document.getElementById(target),
table = document.createElement('table'),
thead = document.createElement('thead'),
header = document.createElement('tr'),
tbody = document.createElement('tbody'),
tfoot = document.createElement('tfoot'),
// totals is used for the totals for the footer
totals = {};
// creates the header
for (const column of columns) {
// and creates the cells in the header, adding title and class
let cell = document.createElement('td');
cell.innerHTML = column.title;
cell.className = column.class;
header.appendChild(cell);
}
thead.appendChild(header);
for (const item of data) {
// creates the single rows
let row = document.createElement('tr');
for (const column of columns) {
// and for each column creates the cell itself
let cell = document.createElement('td');
let value;
// checks what to display
if (column.field) {
// only a property on the data
value = item[column.field];
} else if (column.value) {
// a function with a callback value
value = column.value(item)
}
// if it should calculate totals, it will do so here
if (column.calculateTotal) {
// in case the column is unknown, it's initialized as 0
// warning: all values will be whole numbers
totals[column.field] = (totals[column.field] || 0) + parseInt( value );
}
// if it has a template, we will replace the %0 with value
// this template function supports only 1 value to be "templated"
if (column.template) {
value = column.template.split('%0').join(value);
}
// set the cell value
cell.innerHTML = value;
// set the class (used to align, for example)
cell.className = column.class;
// add cell to row
row.appendChild(cell);
}
// add row to tbody
tbody.appendChild(row);
}
// empty object would mean false, so only if totals needed to be calculated
// would it create the footer here
if (totals && data.length > 0) {
let row = document.createElement('tr');
for (const column of columns) {
let cell = document.createElement('td'), value = '';
if (column.calculateTotal) {
value = totals[column.field];
if (column.template) {
// can still use the row template
value = column.template.split('%0').join(value);
}
}
cell.innerHTML = value;
cell.className = column.class;
row.appendChild( cell );
}
tfoot.appendChild( row );
}
table.appendChild(thead);
table.appendChild(tbody);
table.appendChild(tfoot);
// clear the target element
element.innerHTML = '';
// set the table on the target element
element.appendChild(table);
}
// start of the application, create the 2 tables
// and then it's up to the user
renderProducts();
renderCart();
.left {
text-align: left;
}
.right {
text-align: right;
}
thead tr {
background-color: #777;
}
thead tr td {
font-weight: bold;
color: #fff;
}
tfoot tr td {
font-weight: bold;
}
table td {
padding: 5px;
border-bottom: solid #efefef 1px;
}
.fields > div > span:first-child {
display: inline-block;
width: 120px;
}
.fields > div {
margin: 5px;
}
<div class="fields">
<div>
<span>ItemID:</span>
<span><input type="text" id="productId" placeholder="Item Id" /></span>
</div>
<div>
<span>Description:</span>
<span><input type="text" id="productDescription" placeholder="Product description" /></span>
</div>
<div>
<span>Quantity:</span>
<span><input type="number" min="1" id="productQty" placeholder="Quantity" /></span>
</div>
<div>
<span>Price:</span>
<span><input type="number" min="0" step="0.01" id="productPrice" placeholder="Price" /></span>
</div>
<button type="button" onclick="addProduct()">Add to product list</button>
</div>
<h1>Products</h1>
<div id="products">
</div>
<h1>Shopping cart</h1>
<div id="cart">
</div>

It's been almost 2 years... But the solution is simple actually:
First, try to spit out the name of your product on the console... the value has spaces ! ! !
This means that the cart is adding the raw values without any processing, therefore the condition to match the name and price will never be true, hence the duplicates.
Solution 1 (tedious): Trim spaces of all values prior to adding to the cart.
Solution 2 (preferred): Don't add any spaces between the values and the HTML tags!
"<strong class="item_price">price</strong>"
<!-- "NOT -->
"<strong class="item_price">
price
</strong>"

Related

To update LocalStorage values from a dynamically created HTML table

The HTML table is dynamically created with the function createTableRow(empDetail), which is working but when the rows/cells values are updated/changed it reflects in the HTML table but I want the same respective changed values to get changed in LocalStorage against the respective id. Help is needed for function tableUpdate()
Note: There is only one key i.e. empDetails and the same key has multiple id's of respective rows (Employee) created
"use strict";
let addNewEmployee = document.getElementById("addNewEmployee");
let modal = document.getElementById("favDialog");
let closeModal = document.getElementById("cancelModal");
let modalForm = document.getElementById("modal-form");
let submitModal = document.getElementById("submitModal");
let tableContainerHeader = document.querySelector(".table-container-header");
let tableContainerContent = document.querySelector(".table-container-content");
let empTable = document.getElementById("employeeTable");
const showModal = addNewEmployee.addEventListener("click", function() {
modal.showModal();
});
closeModal.addEventListener("click", function() {
modal.close();
});
let employeeId = document.getElementById("employeeId");
let employeeName = document.getElementById("employeeName");
let designation = document.getElementById("designation");
let salary = document.getElementById("salary");
let uniqueEmpId = document.getElementById("empDetailId");
let tr = null;
let empDetails = [];
if (localStorage.getItem("empDetails")) {
empDetails.map((empDetail) => {
createTableRow(empDetail);
});
}
let onModalSubmit = modal.addEventListener("submit", function(e) {
e.preventDefault();
if (tr == null) {
if (employeeId && employeeName && designation && salary != "") {
let empDetail = {
id: new Date().getTime(),
name: {
employeeIdLocal: employeeId.value,
employeeNameLocal: employeeName.value,
designationLocal: designation.value,
salaryLocal: salary.value,
uniqueEmpId: new Date().getTime(),
},
};
modal.close();
empDetails.push(empDetail);
localStorage.setItem("empDetails", JSON.stringify(empDetails));
modalForm.reset();
createTableRow(empDetail);
}
} else {
tableUpdate(e);
}
});
/////// Create Table Row
function createTableRow(empDetail) {
const tEmployeeMarkup = `
<tr class="fullEmpDetail">
<td id="teId">${empDetail.name.employeeIdLocal}</td>
<td id="teName">${empDetail.name.employeeNameLocal}</td>
<td id="teDesignation">${empDetail.name.designationLocal}</td>
<td id="teSalary">$${empDetail.name.salaryLocal}</td>
<td>
<i class="fas fa-eye"></i>
<i value="Edit" type="button" id="update-row" class="edit-row fas fa-pencil-alt"></i>
<i value="Delete" type="button" class="remove-row fas fa-trash-alt"></i>
</td>
<td id="empDetailId" class="empDetailId">${empDetail.id}</td>
</tr>
`;
empTable.innerHTML += tEmployeeMarkup;
document.getElementById("modal-form").reset();
}
/////// Remove Row
function onDeleteRow(e) {
if (!e.target.classList.contains("remove-row")) {
return;
}
const btn = e.target;
btn.closest("tr").remove();
}
tableContainerContent.addEventListener("click", onDeleteRow);
//////////// Edit Row
tableContainerContent.addEventListener("click", onEditRow);
function onEditRow(e) {
if (e.target.classList.contains("edit-row")) {
modal.showModal();
tr = e.target.parentNode.parentNode;
// console.log(tr);
let tableEmpId = tr.cells[0].textContent;
let tableEmpName = tr.cells[1].textContent;
let tableEmpDesignation = tr.cells[2].textContent;
let tableEmpSalary = tr.cells[3].textContent;
employeeId.value = tableEmpId;
employeeName.value = tableEmpName;
designation.value = tableEmpDesignation;
salary.value = tableEmpSalary;
}
}
///////////////// Update Row
function tableUpdate(e) {
let tableEmpId = document.getElementById("teId");
let tableEmpName = document.getElementById("teName");
let tableEmpDesignation = document.getElementById("teDesignation");
let tableEmpSalary = document.getElementById("teSalary");
tr.cells[0].textContent = employeeId.value;
tr.cells[1].textContent = employeeName.value;
tr.cells[2].textContent = designation.value;
tr.cells[3].textContent = salary.value;
modalForm.reset();
modal.close();
let tableEmpIDs = document.querySelectorAll(".empDetailId");
let empDetails = JSON.parse(localStorage.getItem("empDetails"));
for (let row = 0; row < tableEmpIDs.length; row++) {
for (let i = 0; i < empDetails.length; i++) {
empDetails[i].name.employeeIdLocal = tableEmpId.textContent;
empDetails[i].name.employeeNameLocal = tableEmpName.textContent;
empDetails[i].name.designationLocal = tableEmpDesignation.textContent;
empDetails[i].name.salaryLocal = tableEmpSalary.textContent;
break;
}
}
localStorage.setItem("empDetails", JSON.stringify(empDetails));
}
table
/* th,
td,
tr */
{
border: black solid 1px;
width: 1000px;
text-align: center;
}
table td,
table th {
border: solid 1px black;
width: 200px;
}
table {
border-collapse: collapse;
}
.fas {
margin: 0 10px;
}
.empDetailIdHeader,
.empDetailId {
display: none;
}
<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>Document</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta2/css/all.min.css" integrity="sha512-YWzhKL2whUzgiheMoBFwW8CKV4qpHQAEuvilg9FAn5VJUDwKZZxkJNuGM4XkWuk94WCrrwslk8yWNGmY1EduTA==" crossorigin="anonymous" referrerpolicy="no-referrer"
/>
<link rel="stylesheet" href="./style.css" />
</head>
<body>
<div class="main-container">
<h2>Employee Details</h2>
<button id="addNewEmployee">+ Add New Employee</button>
<div class="table-container-header">
<table>
<tr>
<th>Employee ID</th>
<th>Name</th>
<th>Designation</th>
<th>Salary</th>
<th>Action</th>
<th class="empDetailIdHeader">Local Storage ID</th>
</tr>
</table>
</div>
<div class="table-container-content">
<table id="employeeTable">
</table>
</div>
</div>
<!-- Model Code -->
<div id="#modal-container">
<dialog id="favDialog">
<h2>Enter Employee Details</h2>
<form id="modal-form" method="dialog">
<section>
<p>
<label for="employeeId">Employee ID: </label
><input type="text" id="employeeId" />
</p>
<p>
<label for="employeeName">Name: </label
><input type="text" id="employeeName" />
</p>
<p>
<label for="designation">Designation: </label
><input type="text" id="designation" />
</p>
<p>
<label for="salary">Salary: </label
><input type="text" id="salary" />
</p>
</section>
<menu>
<button id="cancelModal" type="reset">Cancel</button>
<button type="submit" id="submitModal">SUBMIT</button>
</menu>
</form>
</dialog>
</div>
<script src="./script.js"></script>
</body>
</html>
I think you'd be better off just writing the JSON from scratch each time, rather than trying to update it. Something like...
let entries = document.querySelectorAll("table.empDetailsTable tr");
let json = [], props = ['employeeIdLocal', 'employeeNameLocal', 'designationLocal', 'salaryLocal'] // in order that they appear in the table
entries.forEach(row => {
let data = {}
row.querySelectorAll('td').forEach((el, i) => {
data[props[i]] = el.innerText;
})
json.push(data);
})
localStorage.setItem("empDetails", JSON.stringify(data));
I didn't understand your data structure, but hopefully this will suffice or point you in the right direction.
To update LocalStorage values from a dynamically created HTML table
let addNewEmployee = document.getElementById("addNewEmployee");
let modal = document.getElementById("favDialog");
let closeModal = document.getElementById("cancelModal");
let modalForm = document.getElementById("modal-form");
let submitModal = document.getElementById("submitModal");
let tableContainerHeader = document.querySelector(".table-container-header");
let tableContainerContent = document.querySelector(".table-container-content");
let empTable = document.getElementById("employeeTable");
const showModal = addNewEmployee.addEventListener("click", function () {
modal.showModal();
});
closeModal.addEventListener("click", function () {
modal.close();
});
let employeeId = document.getElementById("employeeId");
let employeeName = document.getElementById("employeeName");
let designation = document.getElementById("designation");
let salary = document.getElementById("salary");
let uniqueEmpId = document.getElementById("empDetailId");
let tr = null;
let empDetails = [];
//edit
let editID = "";
let onModalSubmit = modal.addEventListener("submit", function (e) {
e.preventDefault();
if (tr == null && addNewEmployee) {
if (employeeId && employeeName && designation && salary != "") {
let empDetail = {
id: new Date().getTime(),
name: {
employeeIdLocal: employeeId.value,
employeeNameLocal: employeeName.value,
designationLocal: designation.value,
salaryLocal: salary.value,
uniqueEmpId: new Date().getTime(),
},
};
modal.close();
empDetails.push(empDetail);
localStorage.setItem("empDetails", JSON.stringify(empDetails));
modalForm.reset();
createTableRow(empDetail);
}
} else {
tableUpdate(tr);
tr = null;
}
});
/////// Create Table Row
function createTableRow(empDetail) {
const element = document.createElement("tr");
let attr = document.createAttribute("data-id");
attr.value = empDetail.id;
element.setAttributeNode(attr);
element.classList.add("fullEmpDetail");
element.innerHTML = `
<td id="teId">${empDetail.name.employeeIdLocal}</td>
<td id="teName">${empDetail.name.employeeNameLocal}</td>
<td id="teDesignation">${empDetail.name.designationLocal}</td>
<td id="teSalary">$${empDetail.name.salaryLocal}</td>
<td>
<i class="fas fa-eye"></i>
<i value="Edit" type="button" id="update-row" class="edit-row fas fa-pencil-alt"></i>
<i value="Delete" type="button" class="remove-row fas fa-trash-alt"></i>
</td>
`;
empTable.appendChild(element);
document.getElementById("modal-form").reset();
}
/////// Remove Row
function onDeleteRow(e) {
if (!e.target.classList.contains("remove-row")) {
return;
}
const btn = e.target;
btn.closest("tr").remove();
}
tableContainerContent.addEventListener("click", onDeleteRow);
//////////// Edit Row
tableContainerContent.addEventListener("click", onEditRow);
function onEditRow(e) {
if (e.target.classList.contains("edit-row")) {
modal.showModal();
tr = e.target.parentNode.parentNode;
// console.log(tr);
let tableEmpId = tr.cells[0].textContent;
let tableEmpName = tr.cells[1].textContent;
let tableEmpDesignation = tr.cells[2].textContent;
let tableEmpSalary = tr.cells[3].textContent;
employeeId.value = tableEmpId;
employeeName.value = tableEmpName;
designation.value = tableEmpDesignation;
salary.value = tableEmpSalary;
}
}
///////////////// Update Row
function tableUpdate(tr) {
let tableEmpId = document.getElementById("teId");
let tableEmpName = document.getElementById("teName");
let tableEmpDesignation = document.getElementById("teDesignation");
let tableEmpSalary = document.getElementById("teSalary");
console.log(tr);
tr.cells[0].textContent = employeeId.value;
tr.cells[1].textContent = employeeName.value;
tr.cells[2].textContent = designation.value;
tr.cells[3].textContent = salary.value;
editID = tr.dataset.id;
modalForm.reset();
modal.close();
editLocalStorage(editID, tr);
}
///////// Edit Local Storage
function editLocalStorage(editID, tr) {
let empDetails = JSON.parse(localStorage.getItem("empDetails"));
empDetails = empDetails.map((empDetail) => {
if (empDetail.id === parseInt(editID)) {
empDetail.name.employeeIdLocal = tr.cells[0].textContent;
empDetail.name.employeeNameLocal = tr.cells[1].textContent;
empDetail.name.designationLocal = tr.cells[2].textContent;
empDetail.name.salaryLocal = tr.cells[3].textContent;
}
return empDetail;
});
localStorage.setItem("empDetails", JSON.stringify(empDetails));
}

jquery select custom attribute in child controls

I have a table with class="cloneable" that has rows containing one control per row. The controls may be textarea, checkbox, radio, or input[type=number]. Each control has a custom data-answersetid attribute. I would like to select all child controls under the .cloneable class and update each of the data-answersetid attributes to the value of a counter variable, lastanswersetid. I tried the following but it didn't work.
let lastanswersetid: number = 0;
const me: any = $('.cloneable').last(); //there might be several cloneables so just get the last cloneable
$('*[answersetid]', me) ? .each(function(index, value) {
console.log(index, value);
console.log($(this));
lastanswersetid++;
$(this).prop('aswersetid', lastAnswersetid);
}); //set the data-answersetid to the last answerset value
<table id="questionTable" class="table #("Q33,Q65".Contains(Model.QuestionnaireAndChoices.First().QuestionKey) ? "cloneable" : "")">
<tbody>
#foreach (var item in Model.QuestionnaireAndChoices)
{
<tr id="#item.QuestionID"
#("Q64".Contains(item.QuestionKey) ? "class=cloneable" : "Q70,Q71".Contains(item.QuestionKey) ? "class=notCloneable" : "")>
<td>
#Html.DisplayFor(modelItem => item.QuestionKey)
</td>
<td class="question">
#Html.DisplayFor(modelItem => item.Question)
</td>
<td>
#switch (item.ChoiceUIType)
{
case "UI-CHK":
#await Component.InvokeAsync("Checkbox2", new { userID = Model.UserID, questionID = item.QuestionID, questionKey = item.QuestionKey, choiceList = item.Choices });
break;
case "UI-DROP1":
#await Component.InvokeAsync("Dropdown2", new { userID = Model.UserID, questionID = item.QuestionID, questionKey = item.QuestionKey, choiceList = item.Choices });
break;
case "UI-RAD":
#await Component.InvokeAsync("RadioButton2", new { userID = Model.UserID, questionID = item.QuestionID, questionKey = item.QuestionKey, choiceList = item.Choices, question = item.Question, radioGroupName = item.QuestionKey });
break;
case "UI-NUMBER":
{
int thisLength = 10;
string thisUiWidth = "150px";
if (item.QuestionKey == "Q38")
{
thisLength = 3;
}
#await Component.InvokeAsync("Input", new { userID = Model.UserID, questionID = item.QuestionID, questionKey = item.QuestionKey, choiceList = item.Choices, inputLength = thisLength, uiWidth = thisUiWidth });
break;
}
case "UI-TXT":
{
<div class="md-form amber-textarea active-amber-textarea">
<textarea name="#item.QuestionKey" id="#item.QuestionKey" class="md-textarea form-control persistable" rows="3"
data-questionid="#item.QuestionID" data-userid="#Model.UserID"
data-questionkey="#item.QuestionKey"
data-useranswerid="#item.Choices.FirstOrDefault()?.UserAnswerID"
data-codesetid="#item.Choices.FirstOrDefault()?.ChoiceID"
data-oldvalue="#item.Choices.FirstOrDefault()?.OtherDescription"
data-answersetid="#item.Choices.FirstOrDefault()?.AnswerSetID"
data-toggle="tooltip" data-placement="left"
title="#(!string.IsNullOrEmpty(item.Choices.FirstOrDefault()?.OtherDescription) ? string.Format("Answered by {0}", item.Choices.First().AnsweredBy) : "")">#item.Choices.FirstOrDefault()?.OtherDescription</textarea>
</div>
break;
}
default:
{
#*just display the text*#
<div>Render raw text here</div>
break;
}
}
</td>
</tr>
}
</tbody>
</table>
Your selector is wrong, it should be [data-answersetid], also use .data() to set the value.
$('[data-answersetid]', me) ? .each(function(index, value) {
console.log(index, value);
console.log($(this));
lastanswersetid++;
$(this).data('answersetid', lastAnswersetid);
}); //set the data-answersetid to the last answerset value
If you need the actual attribute in the DOm updated you'd need to use .attr()
$(this).attr('data-answersetid', lastAnswersetid);

How do I display a dynamically created html table only once?

Each time I input another football score, the league table is updated and displayed but it's appended to a list of tables. How do I display only the latest table?
Here is an extract of the html:
<div>
<table id="matches" border="1"> </table>
</div>
<div>
<table id="standings" border="1"> </table>
</div>
<input type="button" value="Update" onclick="update()" />
Here is the javascript that displays the fixtures for inputting scores:
// Display fixtures to input the scores
window.onload = function()
{
table = document.getElementById("matches");
var row;
var cell1;
var cell2;
var cell3;
for (i = 1; i < Results.length; i++)
{
row = table.insertRow(i-1); //table starts row 0 but Results row 1 so i-1 used
cell1 = row.insertCell(0);
cell2 = row.insertCell(1);
cell3 = row.insertCell(2);
cell4 = row.insertCell(3);
cell1.innerHTML = Results[i][0];
cell2.innerHTML = '<input type="number" min="0" max="99"/>'
cell3.innerHTML = '<input type="number" min="0" max="99"/>'
cell4.innerHTML = Results[i][3];
}
}
And here is the code that displays the table after the lastest scores have been inputed:
// Display League Table
standings = document.getElementById("standings");
for (i = 0; i < League.length; i++)
{
row = standings.insertRow(i);
cell1 = row.insertCell(0);
cell2 = row.insertCell(1);
cell3 = row.insertCell(2);
cell4 = row.insertCell(3);
cell5 = row.insertCell(4);
cell6 = row.insertCell(5);
cell7 = row.insertCell(6);
cell8 = row.insertCell(7);
cell1.innerHTML = League[i][0];
cell2.innerHTML = League[i][1];
cell3.innerHTML = League[i][2];
cell4.innerHTML = League[i][3];
cell5.innerHTML = League[i][4];
cell6.innerHTML = League[i][5];
cell7.innerHTML = League[i][6];
cell8.innerHTML = League[i][7];
}
After entering three scores this is what is displayed:
I've tried clearing the league array within javascript but still the same outcome. How do I only display top version of the table? Thanks
Thanks again to comments, and some further googling, the following deletes the table ahead of updating it, unless there's a better way?
for(var i = standings.rows.length - 1; i >= 0; i--)
{
standings.deleteRow(i);
}
Cheers everyone! :)
For your table update/question, focus on the updateRow function. This line does the actual update of contents of row rownum column(<td>) i
rows[rownum].getElementsByTagName('td')[i].innerHTML = coldata[i];
There is more here than just updating the table rows, for that you can review the function updateRow in my name-spaced object. updateRow calls createRow if it needs to (the row at that index does not exist), nothing fancy here, then updates the new row.
I use the array of match objects in matches I created (was not one in the question so I made assumptions) also in the namespace:
matches: [{
match: 1,
score: [{
team: "Ap",
score: 3
}, {
team: "Or",
score: 2
}]
}],
Note where I call this code to update the table for standings in the table with standings-table id. I have no idea what those are so I simply inserted some stuff in the array then update the table using
for (let i = 0; i < myLeague.standings.length; i++) {
myLeague.updateRow('standings-table', myLeague.standings[i], i);
}
Other things: I created the form simply to show how to update the table when a new match is inserted, I trigger an event and it does what it needs to update or insert a row - but really that is just to test the update as new matches are created.
Row in a table are either updated or inserted depending totally on the array of matches content
nothing handles deletions from the table or array since this was just about insert and update
if a row index for a match index does not exist, it creates a new row and updates it
var myLeague = myLeague || {
teamSelect1: "team1",
teamSelect2: "team2",
matchesPlayed: 1,
teams: [{
name: "Apples",
abbreviation: "Ap"
},
{
name: "Oranges",
abbreviation: "Or"
},
{
name: "Pears",
abbreviation: "Pe"
}
],
matches: [{
match: 1,
score: [{
team: "Ap",
score: 3
}, {
team: "Or",
score: 2
}]
}],
standings: [
["A", 2, 1, 1, 3, 2, 3, 0],
["B", 3, 1, 1, 3, 2, 3, 6]
],
cloneRow: function(tableid, objectRef) {
// find table to clone/append to
let table = document.getElementById(tableid);
// find row to clone, I use first one
let firstRow = mytable.rows[0];
// let row = document.getElementById("rowToClone");
let clone = firstRow.cloneNode(true); // copy children too
clone.id = ""; // change id or other attributes/contents
table.appendChild(clone); // add new row to end of table
},
createRow: function(tableid, colCount, rowCount = 1, defaultContent = "") {
let row = document.createElement('tr'); // create row node
for (let i = 0; i < colCount; i++) {
let newText = document.createTextNode(defaultContent);
let col = row.insertCell(i);
col.appendChild(newText);
}
let table = document.getElementById(tableid); // find table to append to
let tbody = table.getElementsByTagName('tbody')[0];
for (let r = 1; r <= rowCount; r++) {
tbody.appendChild(row); // append row to table
}
},
updateRow: function(tableid, coldata = ['$nbsp;'], rownum = 0) {
let table = document.getElementById(tableid); // find table to update to
let tbody = table.getElementsByTagName('tbody')[0];
let rows = tbody.rows; // get rows node
let maxRows = 20; //keep it from going crazy adding rows
while (rows.length < maxRows && !rows[rownum]) {
this.createRow(tableid, coldata.length, 1, "x");
}
//myLeague.updateRow(tableid,coldata, rownum);
for (let i = 0; i < coldata.length; i++) {
rows[rownum].getElementsByTagName('td')[i].innerHTML = coldata[i];
}
},
addTeam: function(team, teamid) {
var sel = document.getElementById(teamid);
var optNew = document.createElement("option");
optNew.value = team.abbreviation;
optNew.text = team.name;
sel.add(optNew, null);
},
addTeamsToSelect: function() {
myLeague.teams.forEach(function(team) {
myLeague.addTeam(team, this.teamSelect1);
myLeague.addTeam(team, this.teamSelect2);
}, this);
},
listMatches: function(event) {
// event.target is the div
let src = event.target.dataset.source;
console.log("src:", src);
document.getElementById("matchplayed").textContent = event.matches;
this[src].forEach(function(item, index, array) {
document.getElementById('matchplayed').textContent = array.length;
let rowdata = [item.score[0].team, item.score[0].score, item.score[1].team, item.score[1].score];
this.updateRow(src, rowdata, index);
}, this);
},
clickAddListener: function(event) {
// 'this' is bound to the namespace object
// console.log(event.target); // the button
// console.log(this.matchesPlayed);//namespace
if (!document.getElementById(this.teamSelect1).value || !document.getElementById(this.teamSelect2).value) {
let errorEl = document.getElementById("form1")
.getElementsByClassName("error-text")[0];
errorEl.textContent = "Both teams need to be selected.";
errorEl.style.visibility = 'visible';
errorEl.style.opacity = '1';
setTimeout(function() {
errorEl.style.WebkitTransition = 'visibility .5s, opacity .5s';
errorEl.style.opacity = '0';
errorEl.style.visibility = 'hidden';
errorEl.textContent = "";
}, 5000);
} else {
this.matchesPlayed++;
let r = {
match: this.matchesPlayed,
score: [{
team: document.getElementById(this.teamSelect1).value,
score: document.getElementById("score1").value
}, {
team: document.getElementById(this.teamSelect2).value,
score: document.getElementById("score2").value
}]
};
this.matches.push(r);
}
document.getElementById('matches').dispatchEvent(this.showmatchesevent);
},
addListeners: function() {
let scope = this;
document.getElementById(this.teamSelect1)
.addEventListener('change', function() {
let s = document.getElementById(scope.teamSelect2);
let oval = s.value;
if (this.value == oval) {
s.value = '';
}
}, this);
document.getElementById(this.teamSelect2)
.addEventListener('change', function() {
let s = document.getElementById(scope.teamSelect1);
let oval = s.value;
if (this.value == oval) {
s.value = '';
}
}, this);
document.getElementById('add-match')
// bind this namespace to the event listener function
.addEventListener('click', (this.clickAddListener).bind(this), false);
this.showmatchesevent = new CustomEvent('showmatches');
document.getElementById('matches')
.addEventListener('showmatches', this.listMatches.bind(this), false);
}
};
window.onload = function() {
myLeague.addTeamsToSelect();
myLeague.addListeners();
for (let i = 0; i < myLeague.standings.length; i++) {
myLeague.updateRow('standings-table', myLeague.standings[i], i);
}
// set table from defaults/imported list
document.getElementById('matches').dispatchEvent(myLeague.showmatchesevent);
};
/* typography */
html {
font-family: 'helvetica neue', helvetica, arial, sans-serif;
}
th {
letter-spacing: 2px;
}
td {
letter-spacing: 1px;
}
tbody td {
text-align: center;
}
.match-inputs {
border: solid 2px #DDDDDD;
padding;
1em;
margin: 1em;
}
.error-text {
height: 1em;
color: red;
}
.matches-played {
padding: 13m;
}
/* table layout */
table {
border-collapse: collapse;
border: 1px solid black;
}
.score th,
td {
padding: 0.2em;
border: solid #DDDDDD 1px;
}
.container {
padding: 1em;
}
<div class="container match-inputs">
<form id="form1">
<div>Add Matches</div>
<div class="input-group"><label>Choose L Team:</label>
<select id="team1">
<option value="">Choose</option>
</select>
</div>
<div class="input-group"><label>Choose L2 Team:</label>
<select id="team2">
<option value="">Choose</option>
</select>
</div>
<div class="input-group score-group"><label>Team1 score:</label>
<input id="score1" type="number" class="score-input" value="0" min="0" max="99" value="0" />
</div>
<div class="input-group score-group"><label>Team2 score:</label>
<input id="score2" type="number" class="score-input" value="0" min="0" max="99" value="0" />
</div>
<div class="input-group"><label>Add this match to the list.</label>
<button type="button" id="add-match">Add Match</button>
</div>
<div class="error-text"> </div>
</form>
</div>
<div class="container">
<div class="matches-played">Matches Played:<span id="matchplayed"></span></div>
<table id="matches" data-source="matches">
<thead>
<tr>
<th colspan="4">Matches</th>
</tr>
<tr>
<th>L</th>
<th>S</th>
<th>S2</th>
<th>L1</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
<div class="container">
<table id="standings-table">
<thead>
<tr>
<th colspan="8">Standings</th>
</tr>
<tr>
<th>Team</th>
<th>P</th>
<th>W</th>
<th>D</th>
<th>L</th>
<th>F</th>
<th>A</th>
<th>Pts</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>

Why is <tfoot> added every time when I click on the button ?

When I click on the buttons to delete and add goods (buttAdd / nameDelete), then a new line <tfoot> is added. When I create a table with the array this.products, I also create <tfoot> with the total price of the product, the price of products is considered correct when adding or removing new products.
How to implement <tfoot> added only when I create the table (this is the show () method), and do not create new <tfoot> every time when I press the delete or add buttons?
//Product Creation Class
class Product {
constructor(name, count, price) {
this.name = name;
this.count = count;
this.price = price;
}
}
Product.SORT_ORDER_ASC = 1;
Product.SORT_ORDER_DESC = -1;
// Сlass where products are recorded
class Shop {
constructor() {
this.products = [];
}
//method for adding a product
addProduct(newProduct) {
this.products.push(newProduct);
}
//method for remove product by name
deleteProductByName(productName) {
let i = this.products.length;
while (i--) {
if (productName === this.products[i].name) {
this.products.splice(i, 1);
}
}
}
// get total price by all products
get totalProductsPrice() {
return this.products.map(product => product.price).reduce((p, c) => p + c);
}
//method for sorting the product at its price
sortProductsByPrice(sortOrder) {
const sorted = this.products.sort((a, b) => {
return a.price > b.price ? sortOrder : -sortOrder;
});
return sorted;
}
// method to draw the table with product property (
// name, count, price)
show() {
const rows = document.querySelectorAll("#shop .data");
for (let i = rows.length - 1; i >= 0; i--) {
const e = rows.item(i);
e.parentNode.removeChild(e);
}
const table = document.getElementById("shop");
for (let i = 0; i < this.products.length; i++) {
//create table
table.innerHTML += `<tbody><tr class="data"><td>${this.products[i].name}</td>
<td>${this.products[i].price}</td>
<td>${this.products[i].count}</td></tr></tbody>`;
}
//show total price by all products
table.innerHTML += `<tfoot><tr><td colspan="3" id="total-price">Total price:
${shop.totalProductsPrice}</td></tr></tfoot>`;
}
}
// add new product by click
const formAdd = document.forms[0];
const inputsAdd = formAdd.elements;
const buttAdd = formAdd.elements[3];
buttAdd.addEventListener('click', (e) => {
e.preventDefault();
shop.addProduct(new Product(inputsAdd[0].value, parseInt(inputsAdd[2].value),
parseInt(inputsAdd[1].value)));
shop.show();
}, false);
// delete product by name after click
const formDelete = document.forms[1];
const nameDelete = formDelete.elements[0];
const buttDelete = formDelete.elements[1];
buttDelete.addEventListener('click', (e) => {
e.preventDefault();
shop.deleteProductByName(nameDelete.value);
shop.show();
}, false);
let shop = new Shop();
shop.addProduct(new Product("product", 1, 2000));
shop.addProduct(new Product("product1", 2, 500));
shop.addProduct(new Product("product2", 3, 1000));
shop.show();
const priceFilter = document.getElementById("filter");
//filter products by price
priceFilter.addEventListener("click", (e) => {
shop.sortProductsByPrice(Product.SORT_ORDER_ASC);
shop.show();
}, false);
console.log(shop.products);
<div class="Shop">
<div class="add-product">
<h1>Add product</h1>
<form id="addForm">
<label for="name" >Name of product</label>
<input type="text" id="name" class="input-product">
<label for="price">Price of product</label>
<input type="text" id="price" class="input-product">
<label for="count">Count of product</label>
<input type="text" id="count" class="input-product">
<button id="add">Add</button>
</form>
</div>
<div class="product-table">
<h2>Products</h2>
<form id="delete-form">
<label for="name-delete">Delete product by name</label>
<input type="text" id="name-delete" class="input-delete">
<button id="delete" type="button">Delete</button>
</form>
<table id="shop">
<caption>Products that are available in the store</caption>
<tr>
<th>Name:</th>
<th id="filter">Price:</th>
<th>Count:</th>
</tr>
</table>
</div>
</div>
Because you adding <tfoot> every time you you rendering it with .show. Removing tfoot would fix this issue.
const tfoot = table.querySelector('tfoot');
if(tfoot)
tfoot.remove();
Check JSFiddle

Merge 2 or more cells in a table using React

I am new to React. Trying to create a functionality to merge cells. In normal js I would pass the reference of td to access the styles or others attributes. But here passing this is binding the entire class. How do I take the reference of a particular cell. I also tried passing the index of the cell and row number to the merge function but the click event is not working as expected. Below is the code snippet that I am working on. Throw some light on how to merge 2 cells in a table. Thanks
function getTabularStateFromStore(){
return(
{
row: 0,
col: 0,
rows:[]
}
)
}
class Table extends React.Component{
constructor(){
super();
this.state = getTabularStateFromStore();
this.onChangeOfRow = this.onChangeOfRow.bind(this);
this.onChangeOfCol = this.onChangeOfCol.bind(this);
this.merge = this.merge.bind(this);
}
render(){
let rows = this.state.rows.map((row,index) => {
var cells = [];
for (var i in row) {
cells.push(<td rowSpan={row[i].rowSpan} colSpan={row[i].colSpan} style={{minWidth: "30px", "padding":"5px"}} key={i} onClick={this.merge}></td>);
}
return <tr key={index}>{cells}</tr>;
});
return(
<div>
<div>
<input type="number" size="4" value={this.state.row} default min="0" onChange={this.onChangeOfRow} />
<input type="number" size="4" value={this.state.col} min="0" onChange={this.onChangeOfCol} />
</div>
<table>
<tbody>
{rows}
</tbody>
</table>
</div>
);
}
merge(cell, row) {
//o.style.backgroundColor = "red";
console.log("cell number: "+cell);
console.log("row number: "+row);
}
onChangeOfRow(e) {
this.updateRowsData(Number(e.target.value), this.state.col)
}
onChangeOfCol(e) {
this.updateRowsData(this.state.row, Number(e.target.value))
}
updateRowsData(row, col){
let rows = []
for(var i = 0; i < row; i++){
let cells = []
for(var j = 0; j < col; j++){
cells.push(Object.assign({value:"", rowSpan: 1, colSpan: 1}));
}
rows.push(cells);
}
this.setState({rows: rows, row: row, col: col})
}
}
export default Table;

Categories

Resources