I am trying to store prices of products in sessions, and if a product is clicked twice, the price is should be added and shown in the session, but for some reason when I am trying to add two number , for example 15 + 15, it adds like 01515, I am not sure why this is happening.
Here is the .hbs file where the addition of a product starts.
{{# each products}}
<div class="row">
{{# each this}}
<div class="col-sm-6 col-md-4">
<div class="thumbnail">
<img src="{{this.imagePath}}" alt="..." class="img-responsive">
<div class="caption">
<h3>{{this.title}}</h3>
<p>{{this.description}}</p>
<div class="clearfix">
<div class="price pull-left">${{this.price}}</div>
Add to cart
</div>
</div>
</div>
</div>
{{/each}}
</div>
{{/each}}
After add to cart button is pressed, here is the route and model,
route
router.get('/add-to-cart/:id', function(req, res, next) {
var productId = req.params.id;
var cart = new Cart(req.session.cart ? req.session.cart : {});
Product.findById(productId, function(err, product) {
if (err) {
return res.redirect('/');
}
cart.add(product, product.id);
req.session.cart = cart;
console.log(req.session.cart);
res.redirect('/');
});
});
Model
module.exports = function Cart(oldCart) {
this.items = oldCart.items || {};
this.totalQty = oldCart.totalQty || 0;
this.totalPrice = oldCart.totalPrice || 0;
this.add = function(item, id) {
var storedItem = this.items[id];
if (!storedItem) {
storedItem = this.items[id] = {item: item, qty: 0, price: 0};
}
storedItem.qty++;
storedItem.price = storedItem.item.price * storedItem.qty;
this.totalQty++;
this.totalPrice += storedItem.item.price;
};
this.generateArray = function() {
var arr = [];
for (var id in this.items) {
arr.push(this.items[id]);
}
return arr;
};
};
I tried to log in the console to see whats happening while adding the numbers, it shows like this
Cart {
items:
{ '5855c55482d8722419e21a7d': { item: [Object], qty: 1, price: 15 },
'5855c55482d8722419e21a7f': { item: [Object], qty: 1, price: 15 } },
totalQty: 2,
totalPrice: '01515',
add: [Function],
reduceByOne: [Function],
removeItem: [Function],
generateArray: [Function] }
I am very new to node js, and I am not sure what is going wrong with this.
In your module, change this line
this.totalPrice = oldCart.totalPrice || 0;
to be something like this
this.totalPrice = parseInt(oldCart.totalPrice, 10) || 0;
You should also change
this.totalPrice += storedItem.item.price;
to be something like
this.totalPrice += parseInt(storedItem.item.price);
The issue here is that both this.totalPrice and storedItem.item.price are strings when you first get it from your request — this.totalPrice being a string because oldCart.totalPrice will come back to you as a string.
This is causing coercion on your storedItem.item.price to a string when you add them together:
this.totalPrice += storedItem.item.price;
// string += string => concatenated string
oldcart.totalPrice would be giving you string so the value are getting concatenated.
instead of
this.totalPrice = oldCart.totalPrice || 0;
you could use
this.totalPrice = Number(oldCart.totalPrice) || 0;
Its seems you concat a string value !
try to cast/force the integer type before do any mathematical action
const string1 = '123'
const string2 = '233'
console.log(string1 + string2) //123233
console.log(parseInt(string1) + parseInt(string2)) //356
Always verify if your parseInt do not return a NaN ;)
Related
I do not know how to create a class that will manage the goods.
1. Sort by price
2. Sort by name
I created a constructor that creates objects.
class Products {
constructor(name,price,description,img){
this.name = name;
this.price = price;
this.description = description;
this.img = img;
}
}
var nike = new Products("Nike", 100, "new-shoes","img/nike.png");
var adidas = new Products("Adidas", 120, "classic-shoes","img/adidas.png");
var puma = new Products("Puma",150,"new-shoes","img/puma.png");
var jordan = new Products("Jordan", 170, "outlet-shoes", "img/jordan.png");
var converse = new Products("Converse",70,"outlet-shoes","img/convrse.png")
var nikeAirMax = new Products("Nike Air Max", 200, "shoes","img/nikeAirMax.png");
var newBal = new Products("New Balance 990",179,"new-shoes","img/newBal.png");
var arrGoods = [nike,adidas,puma,jordan,nikeAirMax,converse,newBal];
Then created a function that displays the goods in the HTML file.
function addGoods(item){
for (let i = 0; i<arrGoods.length; i++){
document.getElementById("products").innerHTML += `<div class="info-goods">
<div class="img"><img src=${item[i].img}></div>
<div class="name">${item[i].name}</div>
<div class="price">${item[i].price}</div>
<div class="description">${item[i].description}</div>
</div>`
}
}
addGoods(arrGoods);
created functions that are sorted (by price and by name)
function sortByPrise() {
var div = document.querySelector("#products");
if (div){
div.innerHTML = '';
this.PriseSort(arrGoods);
addGoods(arrGoods);
};
}
function sortByName() {
var div = document.querySelector("#products");
if (div){
div.innerHTML = '';
nameSort(arrGoods);
addGoods(arrGoods);
};
}
function PriseSort(arr){
arr.sort(function(a,b){
return a.price - b.price;
});
};
function nameSort(arr){
arr.sort(function(a,b){
if(a.name > b.name){
return 1;
}
if (a.name < b.name){
return -1;
How to add these functions to another class (for example, class Menedger)
Add all of the data into an array of objects.
Pass a selector string and the array of objects to the constructor. The selector is for the <table> (a table is far better than divs but it can be easily changed if divs are preferred)
Add a method that will add rows from a given array of objects. see .addRows() in demo.
Add a method that sorts an array of objects and passes it along to .addRows(). see .sortCol() in demo.
As a bonus the <th> will sort by column when clicked. The callback isn't part of the class because it's way out of the scope of the question.
const items = [
{name: "Nike", price: 100, desc: "new-shoes", img: "https://www.iconsdb.com/icons/preview/red/nike-xxl.png"},
{name: "Adidas", price: 120, desc: "classic-shoes", img:"https://www.vouchercodes.co.uk/static/v10/images/merchant/logo/128px/1524_160119151356.png"},
{name: "Puma",price: 150,desc: "new-shoes", img:"https://www.freepnglogos.com/uploads/puma-logo-png-1.png"},
{name: "Jordan", price: 170, desc: "outlet-shoes", img:"https://www.svgimages.com/svg-image/s8/air-jordan-logo-256x256.png"},
{name: "Converse",price: 70,desc: "outlet-shoes", img:"https://d13genyhhfmqry.cloudfront.net/widget/mp_340806_2019-04-06-01-57-52-000851.jpg"},
{name: "Nike Air Max",price: 200,desc: "shoes", img:"https://www.iconsdb.com/icons/preview/red/nike-xxl.png"},
{name: "New Balance 990",price: 179,desc: "new-shoes", img:"https://www.vouchercodes.co.uk/static/v10/images/merchant/logo/128px/4484_160309151253.png"}];
class Products {
constructor(selector, items) {
this.table = document.querySelector(selector);
this.items = [...items];
}
addRows(array) {
const rng = document.createRange();
rng.selectNodeContents(this.table);
rng.deleteContents();
for (let item of array) {
let row = `
<tr>
<td><img src='${item.img}' width='50'></td>
<td>${item.name}</td>
<td>${item.price}</td>
<td>${item.desc}</td>
</tr>`;
this.table.insertAdjacentHTML('beforeend', row);
}
}
sortCol(key = 'name', descending = false) {
if (this.items[0].hasOwnProperty(key)) {
const compare = key => {
return (a, b) => {
if (a[key] < b[key]) return descending ? 1 : -1;
if (a[key] > b[key]) return descending ? -1 : 1;
return 0;
};
};
let sorted = this.items.sort(compare(key));
this.addRows(sorted);
}
return false;
}
}
const shoes = new Products('.main', items);
shoes.addRows(items);
const head = document.querySelector('.head');
function callback(e) {
const clicked = e.target;
if (clicked.matches('th')) {
let key = clicked.className;
if (clicked.dataset.dir === '1') {
shoes.sortCol(key, true);
clicked.dataset.dir = '0';
} else {
shoes.sortCol(key);
clicked.dataset.dir = '1';
}
}
return false;
}
head.addEventListener('click', callback);
<table class='catalog'>
<thead class='head'>
<tr>
<th class='name' data-dir='0' colspan='2'>Name</th>
<th class='price' data-dir='0'>Price</th>
<th class='desc' data-dir='0'>Desc</th>
</tr>
</thead>
<tbody class='main'></tbody>
</table>
I would not make the products in the product class. Just keep the product class like:
class Products {
constructor(name, price, description, img){
this.name = name;
this.price = price;
this.description = description;
this.img = img;
}
}
}
Then you can create a Manager class:
class Manager {
constructor() {
this.products = this._createProducts()
}
_createProducts() {
let nike = new Products("Nike", 100, "new-shoes","img/nike.png");
let adidas = new Products("Adidas", 120, "classic-shoes","img/adidas.png");
return [nike, adidas]
}
sortByName() {
this.products.sort(function(prod1, prod2) {
return prod1.name - prod2.name;
});
}
getHTMLRepresentation() {
productsHTML = this.products.map(function(product) {
// return html for each product in the array
return `<div class="info-goods">
<div class="img"><img src=${item[i].img}></div>
<div class="name">${item[i].name}</div>
<div class="price">${item[i].price}</div>
<div class="description">${item[i].description}</div>
</div>`
)));
return productsHTML;
}
}
Now just create an instance of your manager class and call functions on it:
Manager manager = new Manager();
manager.sortByName()
// call a function to create html..
document.querySelector("#products").innerHTML = manager.getHTMLRepresentation();
I have a variable that gets its value once an ID is clicked, via .innerText
var currentID = e.target.id;
I need the value of this variable, currentID, to be stored in a new variable which is named just like the ID of which it got its value.
So, if a user clicks an element with the ID price1, and the price is 200.
A new variable with the name price1 with value 200 should be created.
Then, I want to sum up the new variables: price1+price2+price3 etc = totalprice.
This is what I'm doing right now:
$('div.unselected-option').click(function(e) {
$(this).toggleClass("selected-option unselected-option")
if ($(this).hasClass("selected-option")) {
var currentID = e.target.id;
console.log(currentID);
var price1 = document.getElementById(currentID).innerText
var finalprice
finalprice = +price1;
document.getElementById("showprice2").innerHTML = finalprice
Here's an image of the design:
I can't seem to figure this out... What I'm doing right now just results in having 1 variable which means I cannot sum anything up... I would love your view on this issue!
Your use case is pretty strange I hope your backend is secured and well made.
Here is a potential solution:
<div id="a1" onclick="handleProductClick">20</div>
<div id="a2" onclick="handleProductClick">40</div>
<div id="b1" onclick="handleProductClick">20</div>
<div id="b2" onclick="handleProductClick">60</div>
...
<div id="total-price">0</div>
...
const basket = {}
function addToBasket(event) {
const { id, innerText } = event.target
const price = parseInt(innertText, 10)
const product = basket[id]
const count = product.count || 1
basket[id] = {
price,
count
}
}
function getBasketTotalPrice = () => {
return Object.keys(basket)
.reduce((total, product) => total + product.count * product.price, 0)
}
function handleProductClick = (event) => {
addToBasket(event)
const totalPrice = getBasketTotalPrice()
document.querySelector('#total-price').innerHTML = totalPrice
}
I have a list of object array with id and value as its properties. Basically what I want is, the num.items[i].value should go in each div as pair. One: one and so on.
If num.items[i].id doesn't have the digit (like the array doesn't include id 3) then the id="digit_3" should be left blank.
HTML
<ul>
<li>One: <div id="digit_1">s</div></li>
<li>Two: <div id="digit_2">sd</div></li>
<li>Three: <div id="digit_3">sdf</div></li>
<li>Four: <div id="digit_4">sdf</div></li>
</ul>
Javascript
var num = {
items: [
{ id:4, value:"four"},
{ id:1, value:"one"},
{ id:2, value:"two"},
]
};
for(var i=0; i<num.items.length; i++){
document.getElementById("digit_"+i+1).innerHTML = i+1;
console.log(i+1)
}
Required output
One: one
Two: two
Three:
Four: four
I know we cannot compare the id digit but any modification in HTML is greatly appreciated.
it's really simple - you just have to understand arrays and objects:
var num = {
items: [{
id: 4,
value: "four"
},
{
id: 1,
value: "one"
},
{
id: 2,
value: "two"
},
]
};
var cleanableElements = document.querySelectorAll("ul li div");
for (var i = 0; i < cleanableElements.length; i++) {
cleanableElements[i].innerHTML = '';
}
var index;
for (var i = 0; i < num.items.length; i++) {
index = num.items[i].id;
document.getElementById("digit_" + index).innerHTML = num.items[i].value;
}
<ul>
<li>One:
<div id="digit_1"></div>
</li>
<li>Two:
<div id="digit_2"></div>
</li>
<li>Three:
<div id="digit_3"></div>
</li>
<li>Four:
<div id="digit_4"></div>
</li>
</ul>
Best idea would be to select all elements with querySelectorAll and setting empty before next step. You can't really detect all #digit_X id's so you can't just check for unchanged DIVs as you can't reliably detect them all.
You should loop ul li div then check id whether is in num.items.
Assuming your id format is digit_*.
var num = {
items: [
{ id:4, value:"four"},
{ id:1, value:"one"},
{ id:2, value:"two"},
]
}
function checkItems(num){
items = document.querySelectorAll('#target_div li div')
indexes = num.items.reduce( (pre, cur) => {
pre[cur.id] = cur.value
return pre
}, {}) // loop num.items then create one dict with key=id.
items.forEach(function(item){ //loop ul li div, then check whether id in dict=indexes.
let ids = item.id.split('_')
if(ids[1] in indexes){
item.innerHTML += ' #Found:'+item.id
} else {
item.innerHTML = ''
}
})
}
checkItems(num)
<ul id="target_div">
<li>One: <div id="digit_1">s</div></li>
<li>Two: <div id="digit_2">sd</div></li>
<li>Three: <div id="digit_3">sdf</div></li>
<li>Four: <div id="digit_4">sdf</div></li>
</ul>
I know I did something awkward but if div have already some value then above example will not work expect #sphinx answer I guess
var num = {
items: [{
id: 4,
value: "four"
},
{
id: 1,
value: "one"
},
{
id: 2,
value: "two"
},
]
};
var idsArray = [];
var valuesArray = [];
for (var value of num.items) {
idsArray.push(value.id);
valuesArray.push(value.value);
}
var maxId = Math.max(...idsArray);
for (var i = 1; i <= maxId; i++) {
if (idsArray.indexOf(i) !== -1) {
document.getElementById("digit_" + i).innerHTML = valuesArray[idsArray.indexOf(i)];
} else {
document.getElementById("digit_" + i).innerHTML = "";
}
}
div {
display: inline
}
<ul>
<li>One: <div id="digit_1">s</div></li>
<li>Two: <div id="digit_2">sd</div></li>
<li>Three: <div id="digit_3">sdf</div></li>
<li>Four: <div id="digit_4">sdf</div></li>
</ul>
I have the following array:
$scope.products = [
{
nom : "Pa de fajol",
img : "dill1.jpg",
descripcio: [
"Pa de motlle"
],
preu:4.90,
tipus: "Pa"
},
{
nom : "Pagès semi integral de blat/espelta",
img : "dill2.jpg",
descripcio: [
"Pa de pagès",
"680g / 400g"
],
tipus: "Pa",
variacions : [
{
nom: "Gran",
preu: "3.70",
grams: "680g",
basket: 0
},
{
nom: "Petit",
preu: "2.30",
grams: "400g",
basket: 1
}
]
}
In the frontend I display it with an ng-repeat and depending if the product has price (preu) or not, I display one input for quantity for single product, or one input for each variation.
Looks like this:
<div ng-repeat="pa in products">
<div ng-if="!pa.preu" ng-repeat="vari in pa.variacions">
<input type="number" ng-model="pa.variacions.basket" ng-init="pa.variacions.basket=0">
</div>
<input ng-if="pa.preu" type="number" ng-model="pa.basket" name="basket" ng-init="pa.basket=0">
</div>
<a ng-click="insert()">Add to cart</a>
When I trigger insert(), I have a function that will add the products and calculate the total price, but so far it only works for products with a price, not for the variations.
The function looks like this:
$scope.insert = function() {
$scope.singleorder = [];
for(var i = 0; i < $scope.products.length; i++)
if($scope.products[i].basket) { // if input has been setted
$scope.singleorder.push({
'product': $scope.products[i].nom,
'number': $scope.products[i].basket,
'preu': ($scope.products[i].preu * $scope.products[i].basket)
});
}
console.log($scope.singleorder);
}
How would I have to proceed to include in the $scope.singleorder array also the calculations from the nested array of variations?
Any tips?
EDIT
See Plunkr to reproduce the issue
SOLUTION
I managed to fix it with the help of some comments!
See Plunkr for final version.
Here's the final function:
$scope.insert = function() {
$scope.singleorder = [];
for(var i = 0; i < $scope.products.length; i++)
if($scope.products[i].basket) { // if input has been setted
$scope.singleorder.push({
'product': $scope.products[i].nom,
'number': $scope.products[i].basket,
'preu': ($scope.products[i].preu * $scope.products[i].basket)
});
}
else if($scope.products[i].variacions) { // if input has been setted
angular.forEach($scope.products[i].variacions, function(variacions) {
if(variacions.basket) {
$scope.variname = $scope.products[i].nom + ' (' + variacions.nom + ')'
$scope.singleorder.push({
'product': $scope.variname,
'number': variacions.basket,
'preu': (variacions.preu * variacions.basket)
});
}
});
}
console.log($scope.singleorder);
Your issue was that your variacions is an array use this,
$scope.insert = function() {
$scope.singleorder = [];
for(var i = 0; i < $scope.products.length; i++)
if($scope.products[i].basket) { // if input has been setted
$scope.singleorder.push({
'product': $scope.products[i].nom,
'number': $scope.products[i].basket,
'preu': ($scope.products[i].preu * $scope.products[i].basket)
});
}
else if($scope.products[i].variacions) { // if input has been setted
for(var j=0;j<$scope.products[i].variacions.length; j++){
$scope.singleorder.push({
'product': $scope.products[i].variacions[j].nom,
'number': $scope.products[i].variacions[j].basket,
'preu': ($scope.products[i].variacions[j].preu * $scope.products[i].variacions[j].basket)
});
}
}
console.log($scope.singleorder);
}
See Plunkr
Hello im fairly new to knock out and im trying to have my array cartItems auto select their quantity from a drop down. Here is my code:
VIEW
<div data-bind="foreach: cartItems">
<h3 data-bind="text: fullname"></h3>
<p data-bind="text: sku"></p>
<select data-bind="quantityDropdown: number"></select>
</div>
VIEWMODEL
var number = 50;
ko.bindingHandlers.quantityDropdown = {
update: function (element) {
for (var i = 1; i < number + 1; i++) {
var selectedQty = "";
for (var x = 0; x < self.cartItems().length; x++) {
var itemqty = parseFloat(self.cartItems()[x].qty, 10);
if (i === itemqty) {
selectedQty = " selected='selected'";
}
}
// Add each option element to the select here
$(element).append("<option value='" + i + "' " + selectedQty + " class='quantity'>" + i + "</option>");
}
}
};
Now i put two items in the cart and the drop down appears. But the "selected" number is the same for both items in the cart? I know its because its not item specific. but I'm not sure how to make it item specific in Knockoutjs.
Working example:
http://jsfiddle.net/GSvnh/5085/
view :
<div data-bind="foreach: CartItems">
<h3 data-bind="text: FullName"></h3>
<p data-bind="text: Sku"></p>
<select name="qty" class="form-control" data-bind="foreach: QuantityDropdown ,value:SelectedQuantity">
<option data-bind="value: Value,text:Name"></option>
</select>
</div>
VM:
$(function () {
var MainViewModel = function () {
var self = this;
self.CartItems = ko.observableArray([]);
//For example you get below array of objects as response
var response = [{ fullname: "ABC", sku: "1234567789", qty: 12 },
{ fullname: "AAA", sku: "2323227789", qty: 20 },
{ fullname: "BBB", sku: "2311227789", qty: 33 }
];
//you map your response and for each item you create a new CartItemViewModel
self.CartItems($.map(response, function (item) {
return new CartItemViewModel(item);
}));
}
var CartItemViewModel = function (data) {
var self = this;
var number = 50;
self.FullName = ko.observable(data.fullname);
self.Sku = ko.observable(data.sku);
self.QuantityDropdown = ko.observableArray();
for (var i = 1; i < number + 1; i++) {
self.QuantityDropdown.push({ Value: i, Name: i });
}
self.SelectedQuantity = ko.observable(parseFloat(data.qty, 10));
self.SelectedQuantity.subscribe(function (newValue) {
alert("selected Qty : "+ newValue);
})
}
ko.applyBindings(new MainViewModel());
})