I am creating a javascript cart.
Here's how I add a product to the cart:
function addToCart(id, qty){
var basket = []
basket.push({
product_id: id,
quantity: qty
});
localStorage.setItem('basket', JSON.stringify(basket));
}
Now if id is already there, I am trying only to update current basket from localStorage
When I add the same product id, it's duplicating it.
[{"product_id":"10", "quantity":"1"}, {"product_id":"10", "quantity":"1"}]
I want to increase only quantity like:
[{"product_id":"10", "quantity":"2"}]
Tried few methods with each and for with no luck :(
Any help please?
You're replacing your stored array every time, which won't have the problem you've described unless you don't really have the var basket = [] in your function. But the problem you've described would be caused by not checking for an existing entry with the product ID.
Instead:
Keep your array in memory, not just local storage
Load it from local storage on page load
Update it in local storage when you change your memory copy
Remove it from local storage when the user completes their purchase or clears their basket
For #1 and #2: In a place that's global to your code (but ideally not really global):
var basket = JSON.parse(localStorage.getItem("basket")) || [];
For #3:
function rememberBasket() {
localStorage.setItem('basket', JSON.stringify(basket));
}
function addToCart(id, qty){
// See if product exists
var entry = basket.find(function(e) { return e.product_id == id; });
if (entry) {
entry.quantity += qty; // Or just `= qty` to replace rather than adding
} else {
basket.push({
product_id: id,
quantity: qty
});
}
rememberBasket();
}
For #4, of course:
basket = [];
rememberBasket();
Your original code was all ES5 and earlier, so I stuck to that above, but ES2015+ features would make it more concise.
Solution :
function addToCart(id, qty) {
var newItem = true;
var basket = json.parse(localStorage.getItem('basket'));
basket.forEach(function (item){
if(item.product_id == id) {
item.quantity += qty;
newItem = false;
}
})
if(newItem) {
basket.push({
product_id: id,
quantity: qty
});
localStorage.setItem('basket', JSON.stringify(basket));
}
}
First, you need to try to read the basket out of localStorage instead of starting with an empty array each time. Second, I'd recommend you use an object instead of an array. Your products already have ids, so instead of searching the array each time, just let the language do the key lookup for you. Last, you're missing any treatment of how to update existing items in the cart. Here's how I'd approach that, supporting both adding new items to the cart, and increasing the quantity of existing items.
function addToCart(id, qty){
var basket = JSON.parse(localStorage.getItem('basket')) || {};
if (basket[id]) {
basket[id] += qty;
} else {
basket[id] = qty;
}
localStorage.setItem('basket', JSON.stringify(basket));
console.log(basket);
}
addToCart(1,1);
addToCart(2,1);
addToCart(1,1);
addToCart(3,2);
addToCart(3,1);
// results in:
VM222:9 {1: 1}
VM222:9 {1: 1, 2: 1}
VM222:9 {1: 2, 2: 1}
VM222:9 {1: 2, 2: 1, 3: 2}
VM222:9 {1: 2, 2: 1, 3: 3}
Try this
function addToCart(id, qty){
var basket = JSON.parse(localStorage.getItem('basket'));
var isExists = false;
if(basket === null || basket === undefined){
basket = [];
}
basket.reduce(function(o,i){
if(i.product_id === id){
i.quantity += qty;isExists = true;
}
o.push(i);
return o;
},[]);
if(!isExists){
basket.push({
product_id: id,
quantity: qty
});}
localStorage.setItem('basket', JSON.stringify(basket));
}
Related
I have an object in JavaScript that looks something like this
{
product_id: "2",
product_name: "Drinks"
}
The name of the object is product.
There is an array that contains entries of the above object. So, each array item is an entry of the above object.
On button click I check if an object entry with a particular product_id (that is being searched) exists in the array or not. If the object with the particular product_id does not exist in the array then I have to add this new object in to the array. Whereas if the object entry with the particular product_id exists then first I have add a new property named "qty" to the object and then this object is to be added as the new entry in to the array.
Below is the code on button click.
I console.log() the array to see the result.
When the button is clicked the first time then I get the array entry correctly where it shows the object inside the array.
When the button is clicked the second time then the code goes in to the else condition and a new property (by the name qty) is added to the object and then the object is added in to the array. So, now the array has two object entries (first one is added through if condition and the second one is added through the else condition).
Strangely, the problem is that when the second time button is clicked and the else condition is executed then the code modifies the previous existing object entry (which already is there in the array) and adds qty property in that object entry as well.
Ideally it should treat these two as separate entries and if I modify the second object entry then the first entry (which already exists in the array) should remain as it is (which means without qty property) whereas it modifies the previous entry too and adds new one too.
OnButtonClick() {
if (array.length === 0) {
array.splice(0, 0, product);
}
else {
product.qty = 1;
array.splice(0, 0, this.product);
}
}
Below is the full code:
// First Page: categories.ts sets the existing product object using a service
// then navigates to the second page product.ts
ButtonClick() {
this.Service.setProduct(product);
this.route.navigate(['products']);
}
// Service page: service.ts
export class ManageService {
products: any;
ProductArray: any = [];
constructor() { }
public setProduct(data) {
this.products = data;
}
public getProduct() {
return this.products;
}
}
//Second page: products.ts
// First it gathers the product object details that were passed from previous
// categories.ts using getProduct() method in the service.ts
export class ProductsPage implements OnInit {
product: any = [];
ngOnInit() {
this.product = this.Service.getExtras();
}
ButtonClick(searchid: any) {
// searchid is passed on button click
let findsearchidarr = FindItem(searchid);
if (findsearchidarr[0] === true) {
this.Service.ProductArray[findsearchidarr[1]].quantity =
++this.Service.ProductArray[findsearchidarr[1]].quantity;
this.router.navigate(['categories']);
}
else if (findsearchidarr[0] === false) {
this.product.quantity = 1;
this.Service.ProductArray.splice(0, 0, this.product);
this.router.navigate(['categories']);
}
}
FindItem (searchid: any) {
let i = 0;
let foundarray: any = [];
for (let items of this.Service.ProductArray) {
if (items.search_id.toLowerCase().includes(searchid)) {
foundarray[0] = true;
foundarray[1] = i;
foundarray[2] = items.product_id;
return foundarray;
}
i++;
}
foundarray[0] = false;
foundarray[1] = -1;
foundarray[2] = 0;
return foundarray;
}
}
See the logic below. It adds the quantity property to already existing object otherwise adds a new object to array.
products: any[] = [{
product_id: "2",
product_name: "Drinks"
},
{
product_id: "3",
product_name: "Wafers"
},
{
product_id: "4",
product_name: "Chocolates"
}
];
productIDToSearch:number = 4;
quantityToAdd: number = 20;
let foundIndex = products.findIndex((val) => val.product_id == productIDToSearch);
if(this.foundIndex >= 0) {
this.products[this.foundIndex].quantity = this.quantityToAdd;
}
else {
this.products.push({product_id:productIDToSearch, product_name:'Ice-cream', quantityToAdd: 33});
console.log(products);
}
Equivalent javascript code
var products = [{
product_id: "2",
product_name: "Drinks"
},
{
product_id: "3",
product_name: "Wafers"
},
{
product_id: "4",
product_name: "Chocolates"
}
];
let productIDToSearch = 5;
let quantityToAdd = 20;
let foundIndex = products.findIndex((val) => val.product_id == productIDToSearch);
if(foundIndex >= 0) {
products[foundIndex].quantity = quantityToAdd;
console.log(products);
}
else {
products.push({product_id:productIDToSearch, product_name:'Ice-cream', quantityToAdd: 33});
console.log(products);
}
The issue is that in JavaScript objects are treated by reference and so it creates confusion (at least in my case) when I try to add any property or modify any property inside the array of objects.
The solution I found was to first copy the object to another object so that the "by reference" possibility is ruled out, then use the new "copied" object to mark a new entry. This solved the issue.
I have an array of objects with about 1,500 elements
var a = [
{
'name': 'jug',
'price': 0,
},
{
'name': 'watermelon',
'price': 47,
},
{
'name': 'pizza',
'price': 0,
},
{
'name': 'handkerchief',
'price': 52,
},
..........................
]
I keep updating the array with prices as i get data.
I need to re-order the elements with with prices and keep the ones that were on top in the same order.
If that is not so clear lets say you had a web page with products in a certain order, and prices are loading for the products in batches.
I want to put the prices on top and keep it in that order so the products don't jump around. however when i get prices i would like to push it to the bottom after the last price on the list.
try
a.sort(function(a,b){
var priceA = a.price? a.price : Number.MAX_SAFE_INTEGER;
var priceB = b.price? b.price : Number.MAX_SAFE_INTEGER;
return a.price-b.price;
});
This will ensure that if price is not available, they will stay at the bottom.
In order for this to work, you need to have the indexOfObj, which is the index of the needed object in your array:
var updatedElement = a.splice(indexOfObj, 1); // Remove the element with the updated price
a.push(updatedElement); // Add the new element to the end of the 'a' array.
Ok I'm making a few assumptions here because the question is not very clear to be honest. But I believe you want to do something like this:
(assuming newprices is a batch of update data)
// if product already in list update price, otherwise insert at bottom
var i, index, newprice;
for(i = 0; i<newprices.length; i++) {
newprice = newprices[i];
index = a.findIndex(function(p) { return p.name === newprice.name; });
if(index > -1) { a[index].price = newprice.price; }
else { a.push[newprice]; }
}
Or maybe you want to do something like this:
// put items that get updated prices or are new altogether at the end of the list
var i, index, newprice;
for(i = 0; i<newprices.length; i++) {
newprice = newprices[i];
index = a.findIndex(function(p) { return p.name === newprice.name; });
if(index > -1) { a.splice(index, 1); }
a.push[newprice];
}
But yeah it would definitely help if you state more clearly what you're trying to do...
I am creating a client side shopping cart. Added products are saved in local storage, but how can increase product quantity if it is already added to local storage? I have function that adds product to local store and in this function I increase quantity, but products become duplicated.
Product.prototype.addObjToLocalStore = function() {
var obj = {
'name': this.name,
'qty': this.qty,
'price': this.price,
'total': this.getPrice()
}
var oldItems = JSON.parse(localStorage.getItem('itemsArray')) || [];
for (k in oldItems) {
if(oldItems[k].name == obj.name) {
oldItems[k].qty += parseInt(obj.qty);
localStorage.setItem('itemsArray', JSON.stringify(oldItems[k].qty));
}
}
oldItems.push(obj);
localStorage.setItem('itemsArray', JSON.stringify(oldItems));
}
Well, regardless of whether or not you've found the item, you oldItems.push(obj) to it, duplicating the item.
So what I would do in this case is not use an array, but an object. The key of the object being the item's identifier (in your case, the name), and the value being the item's properties (quantity, price, etc). Like so:
Product.prototype.addObjToLocalStore = function() {
// oldItems is an object, not an array
var oldItems = JSON.parse(localStorage.getItem('itemsObject')) || {};
// If it exists, grab its quantity. If not, 0.
var oldQuantity = (oldItems[this.name] && oldItems[this.name].qty) || 0;
var obj = {
'name': this.name,
'qty': oldQuantity + this.qty,
'price': this.price,
'total': this.getPrice()
}
// Replace the old entry with the new.
// If doesn't exist, will be created.
oldItems[this.name] = obj;
// We're done here. Store the new object.
logalStorage.setItem('itemsObject', JSON.stringify(oldItems));
};
If you want to not duplicate your object delete two last lines:
oldItems.push(obj);
localStorage.setItem('itemsArray', JSON.stringify(oldItems));
Hello i am trying to create a simple client side cart, in my controller when the page loads i define the array objects that will hold my items, if they are undefined or have a length of 0 i set them to '[]' by default:
$scope.cart = JSON.parse($window.localStorage.getItem('cart')) || [];
$scope.cart.items = $scope.cart.items || [];
This is the function that adds the item to the cart:
$scope.addItem = function(item) {
if (item.quantity > 0)
{
var cartItem = {
id: item.id,
description: item.class + ' item' + (item.quantity > 1 ? 's' : '') + ' to ' + $scope.details.name,
quantity: item.quantity
}
// Adding item to cart array
$scope.cart.items.push(cartItem)
// Saving cart to storage
$window.localStorage.setItem('cart', JSON.stringify($scope.cart));
// Checking to see if data has indeed been stored in localstorage
console.log(JSON.parse($window.localStorage.getItem('cart')));
}
}
Now my cart in storage always turns up as empty, there were times i played around with the code and got it to work(dunno what i did) but when i reloaded the page everything got cleared.
You are initializing cart as array but then assigning properties to it.
It should be an object:
$scope.cart = JSON.parse($window.localStorage.getItem('cart')) || {};
Also before you can push to an array within cart that array needs to be defined.
Simplest might be to have all the properties available in one object first:
var newCart = {
tickets:[],
items:[]
}
$scope.cart = JSON.parse($window.localStorage.getItem('cart')) || newCart ;
You should check if there is already a value stored as
if(!$window.localStorage.getItem('cart'))
{
// Saving cart to storage
$window.localStorage.setItem('cart', JSON.stringify($scope.cart));
}
I am using Ionic with AngularJS and I am using a localForage database and AJAX via $http. My app has a news stream that contains data like this:
{
"feed":[
{
"id":"3",
"title":"Ein Hund",
"comments:"1"
},
{
"id":"2",
"title":"Eine Katze",
"comments":"2"
}
],
"ts":"20150907171943"
}
ts stands for Timestamp. My app saves the feed locally via localForage.
When the app starts it first loads the locally saved items:
$localForage.getItem("feed").then(function(val) { vm.feed = val; })
Then, it loads the new or updated items (ts < current timestamp) and merges both the old and new data:
angular.extend(vm.feed, response.data.feed);
Updated items look like this:
{
"feed":[
{
"id":"2",
"title":"Eine Katze",
"comments":"4"
}
],
"ts":"20150907171944"
}
That is, the comments count on feed item 2 has changed from 2 to 4. When I merge the old and new data, vm.feed has two items with id = 2.
Does angularjs has a built-in "merge by id" function, i. e. copy from source to destination (if it is a new element), or otherwise replace the old element? In case angularjs does not have such a function, what's the best way to implement this?
Thanks in advance!
angular.merge(vm.feed, response.data.feed);
// EDIT
Probably, it will not merge correctly, so you have to update all properties manually. Update ts property and then find your object with id and replace it.
There is no builtin, I usually write my own merge function:
(function(){
function itemsToArray(items) {
var result = [];
if (items) {
// items can be a Map, so don't use angular.forEach here
items.forEach(function(item) {
result.push(item);
});
}
return result;
}
function idOf(obj) {
return obj.id;
}
function defaultMerge(newItem, oldItem) {
return angular.merge(oldItem, newItem);
}
function mergeById(oldItems, newItems, idSelector, mergeItem) {
if (mergeItem === undefined) mergeItem = defaultMerge;
if (idSelector === undefined) idSelector = idOf;
// Map retains insertion order
var mapping = new Map();
angular.forEach(oldItems, function(oldItem) {
var key = idSelector(oldItem);
mapping.set(key, oldItem);
});
angular.forEach(newItems, function(newItem) {
var key = idSelector(newItem);
if (mapping.has(key)) {
var oldItem = mapping.get(key);
mapping.set(key, mergeItem(newItem, oldItem));
} else {
// new items are simply added, will be at
// the end of the result list, in order
mapping.set(key, newItem);
}
});
return itemsToArray(mapping);
}
var olds = [
{ id: 1, name: 'old1' },
{ id: 2, name: 'old2' }
];
var news = [
{ id: 3, name: 'new3' },
{ id: 2, name: 'new2' }
];
var merged = mergeById(olds, news);
console.log(merged);
/* Prints
[
{ id: 1, name: 'old1' },
{ id: 2, name: 'new2' },
{ id: 3, name: 'new3' }
];
*/
})();
This builds a Map from the old items by id, merges in the new items, and converts the map back to list. Fortunately the Map object will iterate on the entries in insertion order, according to the specification. You can provide your idSelector and mergeItem functions.
Thanks hege_hegedus. Based on your code, I've written my own and tried to use less loops to speed things up a bit:
function updateCollection(localCollection, fetchedCollection) {
angular.forEach(fetchedCollection, function(item) {
var append = true;
for (var i = 0; i < localCollection.length; i++) {
if (localCollection[i].id == item.id) {
// Replace item
localCollection[i] = item;
append = false;
break;
} else if (localCollection[i].id > item.id) {
// Add new element at the right position, if IDs are descending check for "< item.id" instead
localCollection.splice(i, 0, item);
append = false;
break;
}
}
if (append) {
// Add new element with a higher ID at the end
localCollection.push(item);
// When IDs are descending use .unshift(item) instead
}
});
}
There is still room for improvements, i. e. the iteration through all the objects should use binary search since all items are sorted by id.