JSFiddle - React, Tailwind, and code Playground
by graphettion
HTML
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css">
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<div class="container">
<div class="content">
<div class="row">
<div class="span8">
<h1>Example 3 :: Shopping Cart</h1>
<hr />
<h2>Products</h2>
<hr />
<!-- Iterate over view_mode.products (Observable Array) -->
<ul data-bind="foreach:products" class="thumbnails">
<!-- The HTML within this <ul></ul> block will get rendered for each item in view_model.products -->
<li class="span2">
<div class="thumbnail">
<img src="http://placehold.it/160x100" alt="">
<div class="caption">
<!-- [Product] $data.name (Observable) -->
<h5 data-bind="text:$data.name"></h5>
<!-- [Product] $data.price (Computed Observable) -->
<p>$<span data-bind="text:$data.price().formatMoney(2,'.',',')"></span></p>
<!--
- $parent is a special object used to reach above the scope of the foreach loop we're currently in.
- Events binding `click:$parent.addToCart` executes an Action Method in the View Model
- Knockout automaticlly passes $data as the first parameter, and the `events` object as the second parameter.
-->
<a data-bind="click: $parent.addToCart" href="#" class="btn btn-mini btn-success">
<i class="icon-plus icon-white"></i> Add
</a>
</div>
</div>
</li>
</ul>
<h2>Cart</h2>
<hr />
<div class="row-fluid">
<div class="span8">
<table class="table table-bordered">
<thead>
<tr>
<th...
CSS
ul li {
list-style: none;
}
JavaScript
Number.prototype.formatMoney = function(c, d, t) {
var n = this,
c = isNaN(c = Math.abs(c)) ? 2 : c,
d = d == undefined ? "," : d,
t = t == undefined ? "." : t,
s = n < 0 ? "-" : "",
i = parseInt(n = Math.abs(+n || 0).toFixed(c)) + "",
j = (j = i.length) > 3 ? j % 3 : 0;
return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : "");
};
// CLASS Product
var Product = function(id, name, price) {
this.id = ko.observable(id);
this.name = ko.observable(name);
this.price = ko.observable(price);
};
// CLASS CartItem
var CartItem = function(product, quantity) {
var self = this; // Scope Trick
self.product = ko.observable(product);
self.quantity = ko.observable(quantity || 1);
self.cost = ko.computed(function() {
return self.product().price() * self.quantity();
});
};
// CLASS ViewModel
var ViewModel = function() {
var self = this; // Scope Trick
/**
* Observables
*/
self.sales_tax = ko.observable(0.07);
self.shipping_cost = ko.observable(10.00);
/**
* Observable Arrays
*/
self.cart = ko.observableArray();
self.products = ko.observableArray();
/**
* Computed Observables
*/
self.subtotal = ko.computed(function() {
var subtotal = 0;
$(self.cart()).each(function(index, cart_item) {
subtotal += cart_item.cost();
});
return subtotal;
});
self.tax = ko.computed(function() {
return self.subtotal() * self.sales_tax();
});
self.total = ko.computed(function() {
return self.shipping_cost() + self.subtotal() + self.tax();
});
/**
* Actions
*/
self.addToCart = function(product, event) {
// Instantiate a new CartItem object using the passed
// in `Product` object, and then set a quantity of 1.
var cart_item = new CartItem(product, 1);
// Add the CartItem instance to the self.cart (Observable Array)
self.cart.push(cart_item);
};
...