jsFiddle: http://jsfiddle.net/brandondurham/g3exx/
I’m working on a shopping cart setup and using the following JSON data in my model:
{
"cartItems" : [
{
"itemName" : "Product 1",
"itemDesc" : "One year subscription through 14 November 2013",
"itemType" : "subscription",
"itemPrice" : 299,
"itemFreebies" : false
}, {
"itemName" : "Product 2",
"itemDesc" : "OpenType format",
"itemType" : "desktop",
"itemPrice" : 4499,
"itemFreebies" : [{
"freebieName" : "Product 2-a",
"freebieDesc" : "included with your workstation license",
"freebieOriginalPrice" : 99
}]
}, {
"itemName" : "Product 3",
"itemDesc" : "OpenType format",
"itemType" : "desktop",
"itemPrice" : 8999,
"itemFreebies" : [{
"freebieName" : "Product 3-a",
"freebieDesc" : "included with your workstation license",
"freebieOriginalPrice" : 99
}]
}, {
"itemName" : "Product 4",
"itemDesc" : "OpenType format",
"itemType" : "desktop",
"itemPrice" : 99,
"itemFreebies" : [{
"freebieName" : "Product 4-a",
"freebieDesc" : "included with your workstation license",
"freebieOriginalPrice" : 99
}]
}, {
"itemName" : "Product 5",
"itemDesc" : "",
"itemType" : "webfont",
"itemPrice" : 49,
"itemFreebies" : false
}, {
"itemName" : "Product 6",
"itemDesc" : "for use with Cloud.typography",
"itemType" : "webfont",
"itemPrice" : 99,
"itemFreebies" : false
}
]
}
There are some relationships between some of the types of items in the cart. For instance, the first item in this JSON is of the itemType “subscription”. When a user has a subscription in their cart they get some free things, depending on what else is in their cart (hence the nested itemFreebies array in some of the items). They are also allowed to add certain types of items that relate to this subscription (namely “webfonts”). As such, when the user removes the subscription from their cart all of these free items and webfonts must also be removed. Here is the function I am currently using to do this:
removeRelatives: function (itemType) {
var siblings = CartModel.cartItems();
switch (itemType) {
case "subscription":
CartModel.cartItems.remove(function(item) { return item.itemType() == "webfont" });
$.each(CartModel.cartItems(), function(index, sib) {
if (sib.itemFreebies) {
sib.itemFreebies.removeAll();
}
});
break;
}
}
This line removes any root level items that match the “webfont” `itemType:
CartModel.cartItems.remove(function(item) { return item.itemType() == "webfont" });
And this second bit cycles through all cartItems and searches for a itemFreebies nodes that’s not false and removes it:
$.each(CartModel.cartItems(), function(index, sib) {
if (sib.itemFreebies) {
sib.itemFreebies.removeAll();
}
});
It’s this second bit here that I’m not sure about. It’s working, but I suspect it’s not the cleanest way to go about it. In addition the beforeRemove animation I have in place for those nested itemFreebies isn’t working. When removing they just disappear from the screen.
Here is the HTML side of things:
<ul id="cart-contents" data-bind="template: { name: 'cart-item-template', foreach: cartItems, beforeRemove: CartPackage.beforeRemove }">
</ul>
<div id="running-totals">
<div class="totals" data-bind="visible: cartItems().length > 0">
<div><strong>Subtotal</strong></div>
<div><span class="denomination">USD</span> <strong id="subtotal"><span data-bind="formatUSD: CartPackage.totalSurcharge()"></span></strong></div>
</div>
<div class="empty-cart">There are currently no items in your shopping cart.</div>
</div>
<div class="call-to-action" data-bind="visible: cartItems().length > 0">
<div class="split">
<div class="messages"> </div>
<div class="actions">
<button class="cancel">Continue Shopping</button>
<button class="action">Checkout</button>
</div>
</div>
</div>
<input type="hidden" value="" data-bind="value: cartItems().length">
<script type="text/html" id="cart-item-template">
<li>
<button class="delete-item">Delete</button>
<ul>
<li data-bind="attr: {'class': itemType}">
<div class="details">
<h5><strong data-bind="text: itemName">Product Name</strong><!-- ko if: itemType() === 'desktop' --> Desktop fonts<!-- /ko --><!-- ko if: itemType() === 'webfont' --> Webfonts<!-- /ko --></h5>
<p data-bind="text: itemDesc">One year subscription through 14 November 2013</p>
</div>
<div class="quantity">
<!-- ko if: itemType() === "subscription" --><select data-bind='options: SubscriptionData, optionsText: "name", optionsValue: "price", value: itemPrice'></select><!-- /ko -->
<!-- ko if: itemType() === "desktop" || itemType() === "webfont" --><select data-bind='options: DesktopData, optionsText: "name", optionsValue: "price", value: itemPrice'></select><!-- /ko -->
</div>
<div class="cost" data-bind="formatUSD: itemPrice">$ 0</div>
</li>
<!-- ko if: itemFreebies -->
<!-- ko foreach: itemFreebies, beforeRemove: CartPackage.beforeRemove -->
<li class="webfont">
<div class="details">
<h5><strong data-bind="text: freebieName">Product</strong> Webfonts</h5>
<p data-bind="text: freebieDesc">Included with your workstation license</p>
</div>
<div class="quantity"> </div>
<div class="cost">
<span class="original-price" data-bind="formatUSD: freebieOriginalPrice">$ 49.00</span> <span class="free">FREE!</span>
</div>
</li>
<!-- /ko -->
<!-- /ko -->
</ul>
</li>
</script>
Suggestions as to why my beforeRemove isn’t working? Cleaner way to achieve what I’m trying to do?
Thanks! Let me know if you need to see more code…
For the
beforeRemoveanimation, it looks like it was just the syntax issue that I listed in the comment above.Seems to be working appropriately from what I can tell now: http://jsfiddle.net/rniemeyer/g3exx/3/
For the code that removes the freebies, you can simplify it to:
For the relationship piece, I don’t think that there is a simple way to model it better than what you are doing now. One thought would be to create computed observables on your root view model that represent any overall concepts like
hasSubscription. Then, each cart item could subscribe tohasSubscriptionand remove their own freebies, if that value changes. It would at least add some indirection between the subscription cart item and the other cart items. However, it would increate the complexity of the way you would have to map your items.I would be happy to help further, if you want to pursue something like this option.