djslapdash djslapdash - 17 days ago 4
jQuery Question

Knockout JS shopping cart exercise

I am learning knockout js and have begun building a simple shopping cart.

This shopping cart basically ask the user to select a category from a dropdownlist and then a second dropdownlist is then populated with products.

When the user selected a product, the product information is displayed i.e. name/price/qty/total. An 'add Item' button is also visible.

the qty data is a textbook where the user can increment the value. If the user increments the qty value the total value will calculate the new total i.e. (price * qty).

When the user clicks the add item button the product id, name, qty and total are stored and displayed (except id) in an adjacent div i.e. cart list.

The problem i am having is that when an item is in the shopping cart with qty 1 and i want to add a new item to the cart with a qty of 2. The qty value in the cart is also changing to 2. The values should stay at 1 also the total value in the shopping cart is matching that of the new item.

here is the code:

<div id="exp2">
<div>
<span>
<select id="ddlCat" data-bind="options: lstCategories, optionsText: 'name',
optionsValue: 'id', optionsCaption: 'Select Category...',
value: selectedCate"></select>
</span>
<span data-bind="visible: lstProducts().length > 0">
<select id="ddlProd" data-bind="options: lstProducts, optionsText: 'name',
optionsValue: 'id', optionsCaption: 'Select Product...',
value: selectedProdId"></select>
</span>
<span data-bind="with: selectedProd()">
Price: £<span id="pPrice" data-bind="text: price"></span>, &nbsp;
Qty <input type="text" id="pQty" data-bind="value: quantity" style="width:30px;"
placeholder="" required />
SubTotal: £<span data-bind="text: itemTotal()"></span>
<span><button id="btnAdd" class="btnAdd" data-bind="click: addItem">Add to cart</button></span>
</span>
</div>
<div>
<h3>Items In Cart</h3>
<ul id="lstCart" data-bind="foreach: cart">
<li>
Name: <span data-bind="text: name"></span>&nbsp;
Qty: <span data-bind="text: qty"></span>&nbsp;
Item Total: £<span data-bind="text: itemTotal"></span>&nbsp;
</li>
</ul>
Sub Total: £<span data-bind="text: subTotal()"></span>
</div>
</div>


Javascript

var categories = [];
var products = [];
var cartLines = [];

$.ajax({
url: '../KnockoutTut/page5GetCat',
type: "GET", cache: false, async: false,
contentType: "application/json; charset=utf-8",
dataType: "json", traditional: true,
success: function (data) {
//alert('Process Successful');
for (var i = 0; i < data.length; i++) {
var id = data[i].id; var name = data[i].name;
//var nCat = new Category(id, name);
categories.push( new Category(id,name));
}
},
error: function (jqXHR, textStatus, errorThrown) {
//alert("Error")
alert(jqXHR.status + "," + jqXHR.responseText + "," + errorThrown);
}
});

function getProd(catId) {
products = [];
var value = { 'value': catId };
var json = JSON.stringify(value);
$.ajax({
url: '../KnockoutTut/page5GetProd',
type: "POST", data: json, cache: false, async: false,
contentType: "application/json; charset=utf-8",
dataType: "json", traditional: true,
success: function (data) {
//alert('Process Successful');
for (var i = 0; i < data.length; i++) {
var id = data[i].id; var name = data[i].name;
var price = data[i].price; var qty = data[i].qty;
products.push(new Product(id, name, price, 1));
}
},
error: function (jqXHR, textStatus, errorThrown) {
//alert("Error")
alert(jqXHR.status + "," + jqXHR.responseText + "," + errorThrown);
}
});
}

function Category(id, name) {
this.id = id;
this.name = name;
};

function Item(id, name, qty, itemTotal) {
this.id = id;
this.name = name;
this.qty = qty;
this.itemTotal = itemTotal;
};

function Product(id, name, price, qty) {
this.id = id;
this.name = name;
this.price = price;
this.qty = qty;
};

function viewModel() {

var self = this;
self.lstCategories = ko.observableArray(categories);
self.lstProducts = ko.observableArray([]);
self.cart = ko.observableArray([]);
self.selectedCate = ko.observable();
self.selectedProdId = ko.observable();
self.selectedProd = ko.observable();
self.quantity = ko.observable(1);
self.catQty = ko.observable();

self.itemTotal = ko.pureComputed(function () {
return self.selectedProd() ? self.selectedProd().price * parseInt("0" + self.quantity(), 10) : 0;
});

self.subTotal = ko.pureComputed(function () {
var total = 0;
$.each(self.cart(), function () { total += this.itemTotal() })
return total;
});

self.selectedCate.subscribe(function (pCatId) {
var catId = pCatId;
if ($.isNumeric(catId)) {
getProd(catId);
}
self.lstProducts(products);
});

self.selectedProdId.subscribe(function (pProdId) {
var pId = pProdId;
var match = ko.utils.arrayFirst(lstProducts(), function (item) {
return pId === item.id;
});
self.selectedProd(match);
//alert(selectedProd().qty);
});

self.addItem = function (item) {
var nId = item.id; var nName = item.name; var cartQty = quantity; var iTot = itemTotal;
cart.push(new Item(nId, nName, cartQty, iTot));
//cart.push(cartLines);
};
};
ko.applyBindings(viewModel, document.getElementById("exp2"));


Remember i am new to knockout js. so please excuse the bad coding. Thanks

gkb gkb
Answer

The issues seems to be with what value are you using for showing the data after an item has been added. If I am not wrong, you are assigning observable to the property cartQty inside of the cart array. Since, you are using the added values in cart just to show as label information and user is not supposed to change that (unless of course after deleting that record and then adding a new one after the desired modifications), there is no need to use two way binding and hence no need to assign observable like you are doing here -

var cartQty = quantity;

So, it is always better to separate these by assigning only the value enclosed by the observable, rather than the observable itself.

You can try something like this -

var cartQty = quantity();

So that changing the quantity does not have its side effects elsewhere.

Comments