On Github dra1n / knockout-in-action
Bakuta Andrey
{ characters: [ { name: "Kratos", description: "Total badass" }, { name: "Nathan Drake", description: "Treasure hunter and fortune seeker" } ] }
var ViewModel = { name: "Kratos", currentActivity: ko.observable("ripping off sombody's head") }
<p>Oh no! This is <span data-bind="text: name"></span></p> <p>And he is <span data-bind="text: currentActivity"></span></p>
ko.applyBindings(ViewModel);
Oh no! This is Kratos
And he is ripping off sombody's head
Let's solve some problem
<div class="product"> <img src="./images/consoles/xbox.png" alt="xbox"> <div> <h3>Xbox One</h3> <p> Be first to experience Xbox One. The Day One Edition features a commemorative controller and an exclusive achievement. </p> </div> <div class="price">$499</div> <input name="product[xbox]" type="text" value="1"> <span class="delete"> </span></div> ... <div> <div class="total"> Total: <span class="total-amount">$1198</span> </div> <div> <div class="checkout"> <a href="#">Checkout</a> </div> </div> </div>
(function () { var data = [ { title: 'Xbox One', description: 'Be first to experience Xbox One...', price: '$499', quantity: 1, img: './images/consoles/xbox.png' }, ... ] function CheckoutViewModel(data) { ko.mapping.fromJS({ products: data }, {}, this); } $(function() { ko.applyBindings(new CheckoutViewModel(data)); }) })();
<!-- ko foreach: products --> <div class="product"> <img data-bind="attr: { src: img, alt: title }" /> <div> <h3 data-bind="text: title"></h3> <p data-bind="text: description"></p> </div> <div class="price" data-bind="text: price"></div> <input data-bind="value: quantity" type="text" /> <span class="delete" /> </div> <!-- /ko --> <div> <div class="total"> Total: <span class="total-amount">$1198</span> </div> <div> <div class="checkout"> <a href="#" class="btn">Checkout</a> </div> </div> </div>
var data = [ { title: 'Xbox One', description: 'Be first to experience Xbox One...', price: '$499', quantity: 1, img: './images/consoles/xbox.png' }, ... ]
function ProductViewModel(data) { ko.mapping.fromJS(data, {}, this); this.formattedPrice = ko.computed(function() { return '$' + this.price(); }, this); }
function CheckoutViewModel(data) { var mapping = { products: { create: function(options) { return new ProductViewModel(options.data); } } } ko.mapping.fromJS({ products: data }, mapping, this); }
<!-- ko foreach: products --> <div class="product"> <img data-bind="attr: { src: img, alt: title }" /> <div> <h3 data-bind="text: title"></h3> <p data-bind="text: description"></p> </div> <div class="price" data-bind="text: formattedPrice"></div> <input data-bind="value: quantity" type="text" /> <span class="delete" /> </div> <!-- /ko -->
var formatMoney = function(value) { return '$' + value; }
function CheckoutViewModel(data) { ... this.total = ko.computed(function() { var total = 0; ko.utils.arrayForEach(this.products(), function(product) { total += product.price * product.quantity(); }); return formatMoney(total); }, this); }
<div> <div class="total"> Total: <span data-bind="text: total" class="total-amount"></span> </div> <div> <div class="checkout"> <a href="#" class="btn">Checkout</a> </div> </div> </div>
function ProductViewModel(data) { var mapping = { observe: ['quantity'] } ko.mapping.fromJS(data, mapping, this); this.formattedPrice = ko.computed(function() { return formatMoney(this.price) }, this); }
var clearFormat = function(price) { return parseFloat(price.replace(/[^0-9-.]/g, '')); }, formatMoney = function(price) { return '$' + price; }, storePrices = function() { $('.price').each(function() { $(this).next('input').data('price', clearFormat($(this).text())); }); };
var calculateSubtotal = function(e) { var product = e.originalEvent.currentTarget, price = $('.price', product), subtotal = formatMoney($(this).data('price') * parseInt($(this).val())); price.html(subtotal); }, calculateTotal = function() { var total = 0; $('input').each(function() { var $this = $(this); total += $this.data('price') * parseInt($this.val()); }); $('.total-amount').text(formatMoney(total)); }, $('.product').on('change', 'input', function(e) { calculateSubtotal.call(this, e); calculateTotal.call(this, e); });
this.subtotal = ko.computed(function() { return formatMoney(this.price * this.quantity()); }, this);Fix view
<div class="subtotal" data-bind="text: subtotal"></div>And use it when calculating total
this.total = ko.computed(function() { var total = 0; ko.utils.arrayForEach(this.products(), function(product) { total += product.subtotal(); }); return total; }, this)
ko.extenders.formatMoney = function(target) { target.formatMoney = ko.computed(function() { return '$' + ko.utils.unwrapObservable(this); }, target); return target; };
function ProductViewModel(data) { ... this.subtotal = ko.computed(function() { return this.price * this.quantity(); }, this).extend({ formatMoney: true }); } function CheckoutViewModel(data) { ... this.total = ko.computed(function() { var total = 0; ko.utils.arrayForEach(this.products(), function(product) { total += product.subtotal(); }); return total; }, this).extend({ formatMoney: true }); }
<div class="total"> Total: <span data-bind="text: total.formatMoney" class="total-amount"></span> </div> ... <div data-bind="text: subtotal.formatMoney" class="subtotal"></div>
$('.product').on('click', '.delete', function(e) { e.preventDefault(); $(e.originalEvent.currentTarget).remove(); calculateTotal(); });
function CheckoutViewModel(data) { ... this.delete = function(product) { this.products.remove(product); } }
<a href="#" data-bind="click: $parent.delete" class="delete"></a>
function CheckoutViewModel(data) { ... this.checkout = function() { var mapping = { ignore: ['description', 'img', 'price'] }; $.ajax({ dataType: 'json', data: ko.mapping.toJSON(this, mapping), type: 'post' }) .success(function() { alert('Thank you for your order'); }); } }
<a data-bind="click: checkout" href="#" class="btn">Checkout</a>
$('.checkout a').on('click', function(e) { e.preventDefault(); $.ajax({ dataType: 'json', data: JSON.stringify($('form').serializeArray()), type: 'post' }) .success(function() { alert('Thank you for your order'); }); });