Compare commits

..

19 Commits

Author SHA1 Message Date
Jason Zhu 5f833290b6 chap02 -> Managing templates with koExternalTemplateEngine 2022-04-29 23:39:08 +10:00
Jason Zhu fe598fc132 Did a little bit of change 2022-04-21 00:48:24 +10:00
Jason Zhu 708353b7f8 Prettified all html and js 2022-04-21 00:34:04 +10:00
Jason Zhu 1b13bd0e0c Chap02 -> Managing template with jQuery (Bug fixing) 2022-04-21 00:32:44 +10:00
Jason Zhu 9b7f2fefbe Chap02 -> Managing templates with jQuery 2022-04-20 23:53:23 +10:00
Jason Zhu 0e06cb0806 chap02 -> Handling templates with if and ifnot bindings 2022-04-20 23:44:00 +10:00
Jason Zhu e078b83455 chap02 -> Creating templates -> The finish-order-modal template 2022-04-20 22:36:33 +10:00
Jason Zhu 93dae838eb chap02 -> Creating templates -> The order template 2022-04-20 22:33:50 +10:00
Jason Zhu 7256b15da7 chap02 -> Creating template -> The cart template 2022-04-20 22:04:15 +10:00
Jason Zhu a9fdd8d516 chap2 -> Creating templates -> The cart-item template 2022-04-20 21:54:35 +10:00
Jason Zhu 16d677a98c chap02 -> Creating templates -> The cart-widget template (Bug Fix) 2022-04-20 21:38:38 +10:00
Jason Zhu b1fc664fe9 chap02 -> Creating templates -> The cart-widget template 2022-04-20 21:30:56 +10:00
Jason Zhu b299143ee2 chap02 -> Creating templates -> The add-to-cart-modal template 2022-04-20 19:25:18 +10:00
Jason Zhu e52b7bb978 chap2 -> Creating templates -> The catalog template -> part 3
Add CartProduct.js
Add addToCart method in viewmodel
Rename product.js to Product.js
2022-04-19 23:38:16 +10:00
Jason Zhu d18a1efd31 chap2 -> Creating templates -> The catalog template -> part 2
Move logic of determine color into product.js

Note: This change is not completed
2022-04-19 22:57:03 +10:00
Jason Zhu 3001e6f436 chap2 -> Creating templates -> The catalog template -> part 1
Adding catalog template in simplest way
2022-04-19 22:39:27 +10:00
jason.zhu 4b8c3312f0 chap2->Creating templates->The header template 2021-05-27 05:43:48 +00:00
jason.zhu 61c3776031 chap2->Creating templates: define templates in index.html 2021-05-27 05:31:01 +00:00
jason.zhu ee6c23d6d3 chap2. KnockoutJS Templates->Preparing the project 2021-05-27 05:23:23 +00:00
18 changed files with 807 additions and 134 deletions

View File

@ -0,0 +1,29 @@
.container-fluid {
margin-top: 20px;
}
.row {
margin-bottom: 20px;
}
.cart-unit {
width: 80px;
}
.btn-xs {
font-size: 8px;
}
.list-group-item {
overflow: hidden;
}
.list-group-item h4 {
float: left;
width: 100px;
}
.list-group-item .input-group-addon {
padding: 0;
}
.btn-group-vertical > .btn-default {
border-color: transparent;
}
.form-control[disabled],
.form-control[readonly] {
background-color: transparent !important;
}

View File

@ -1,96 +1,35 @@
<!DOCTYPE html>
<html>
<head>
<title>KO Shopping Cart</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css" />
</head>
<body>
<h1>Catalog</h1>
<h2>Insert</h2>
<form class="form-horizontal" role="form" data-bind="with:newProduct">
<div class="form-group">
<div class="col-sm-12">
<input
type="text"
class="form-control"
placeholder="Name"
data-bind="textInput:name"
/>
<head>
<title>KO Shopping Cart</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css" />
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div class="container-fluid">
<div class="row" data-bind="if: visibleCatalog">
<div class="col-xs-12" data-bind="template:{name:'header'}"></div>
<div class="col-xs-6" data-bind="template:{name:'catalog'}"></div>
<div class="col-xs-6" data-bind="if: visibleCart">
<div class="well" data-bind="template:{name:'cart'}"></div>
</div>
</div>
<!-- <div class="row" data-bind="ifnot: visibleCatalog">
<div data-bind="template:{name:'order'}"></div>
</div>
<div data-bind="template: {name:'add-to-catalog-modal'}"></div>
<div data-bind="template: {name:'finish-order-modal'}"></div> -->
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<input
type="password"
class="form-control"
placeholder="Price"
data-bind="textInput:price"
/>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<input
type="password"
class="form-control"
placeholder="Stock"
data-bind="textInput:stock"
/>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<button
type="submit"
class="btn btn-default"
data-bind="{click:$parent.addProduct}"
>
<i class="glyphicon glyphicon-plus-sign"> </i> Add Product
</button>
</div>
</div>
</form>
<h2>Search</h2>
<div class="input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-search"></i> Search</span
>
<input
type="text"
class="form-control"
data-bind="textInput: searchTerm"
/>
</div>
<h2>Products</h2>
<strong>Items:</strong>
<span data-bind="text:catalog().length"></span>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Stock</th>
</tr>
</thead>
<tbody data-bind="foreach:catalog">
<tr>
<td data-bind="text:name"></td>
<td data-bind="text:price"></td>
<td data-bind="text:stock"></td>
</tr>
</tbody>
</table>
<!-- vendor library -->
<script type="text/javascript" src="js/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="js/bootstrap.js"></script>
<script type="text/javascript" src="js/knockout-3.2.0.js"></script>
<!-- app -->
<script type="text/javascript" src="js/models/product.js"></script>
<script type="text/javascript" src="js/viewmodel.js"></script>
</body>
<!-- vendor library -->
<script type="text/javascript" src="js/vendors/jquery-2.1.1.min.js"></script>
<script type="text/javascript" src="js/vendors/bootstrap.js"></script>
<script type="text/javascript" src="js/vendors/knockout-3.2.0.js"></script>
<script type="text/javascript" src="js/vendors/koExternalTemplateEngine_all.js"></script>
<!-- app -->
<script type="text/javascript" src="js/models/Product.js"></script>
<script type="text/javascript" src="js/models/CartProduct.js"></script>
<script type="text/javascript" src="js/viewmodel.js"></script>
</body>
</html>

View File

@ -0,0 +1,39 @@
var CartProduct = function (product, units) {
"unit strict";
// Each cart product is composed of the product and units of the product we want to buy
var _product = product,
_units = ko.observable(units); // number of units to add or remove
var subtotal = ko.computed(function() {
return _product.price() * _units();
});
var addUnit = function () {
var u = _units(); // read number of units from observable
var _stock = product.stock();
if (_stock === 0 ) {
return;
}
_units(u + 1); // assign observable with new unit
_product.stock(--_stock); // reduce inventory of stock by decrease unit from product
};
var removeUnit = function () {
var u = _units();
var _stock = _product.stock();
if (u === 0) {
return;
}
_units(u - 1);
_product.stock(++_stock);
};
return {
product: _product,
units: _units,
subtotal: subtotal,
addUnit: addUnit,
removeUnit: removeUnit
}
}

View File

@ -5,10 +5,14 @@ var Product = function (id, name, price, stock) {
_name = ko.observable(name),
_price = ko.observable(price),
_stock = ko.observable(stock);
var _lineColor = ko.computed(function() {
return (_stock() < 5) ? 'red' : 'black';
});
return {
id: _id,
name: _name,
price: _price,
stock: _stock,
lineColor: _lineColor
};
};

View File

@ -0,0 +1,317 @@
(function($, undefined) {
/*
TrafficCop
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.3.0
*/
var inProgress = {};
$.trafficCop = function(url, options) {
var reqOptions = url, key;
if(arguments.length === 2) {
reqOptions = $.extend(true, options, { url: url });
}
key = JSON.stringify(reqOptions);
if (key in inProgress) {
for (var i in {success: 1, error: 1, complete: 1}) {
inProgress[key][i](reqOptions[i]);
}
} else {
inProgress[key] = $.ajax(reqOptions).always(function () { delete inProgress[key]; });
}
return inProgress[key];
};
})(jQuery);
(function($, global, undefined) {
/*
infuser.js
Author: Jim Cowart
License: Dual licensed MIT (http://www.opensource.org/licenses/mit-license) & GPL (http://www.opensource.org/licenses/gpl-license)
Version 0.2.0
*/
var hashStorage = {
templates: {},
storeTemplate: function(templateId, templateContent) {
this.templates[templateId] = templateContent;
},
getTemplate: function(templateId) {
return this.templates[templateId];
},
purge: function() {
this.templates = {};
}
};
var scriptStorage = {
templateIds: [],
storeTemplate: function(templateId, templateContent) {
var node = document.getElementById(templateId);
if(node === null) {
this.templateIds.push(templateId);
node = document.createElement("script");
node.type = "text/html";
node.id = templateId;
document.body.appendChild(node);
}
node.text = templateContent;
},
getTemplate: function(templateId) {
return document.getElementById(templateId);
},
purge: function() {
for(var i = 0; i < this.templateIds.length; i++) {
document.body.removeChild(document.getElementById(this.templateIds[i]));
}
this.templateIds = [];
}
};
var errorHtml = "<div class='infuser-error'>The template <a href='{TEMPLATEURL}'>{TEMPLATEID}</a> could not be loaded. {STATUS}</div>",
returnErrorTemplate = function(status, templateId, templatePath) {
return errorHtml.replace('{STATUS}', status).replace('{TEMPLATEID}', templateId).replace('{TEMPLATEURL}', templatePath);
},
errors = [];
var helpers = {
getTemplatePath: function(templateOptions) {
var templateFile = templateOptions.templatePrefix + templateOptions.templateId + templateOptions.templateSuffix;
return templateOptions.templateUrl === undefined || templateOptions.templateUrl === "" ?
templateFile : templateOptions.templateUrl + "/" + templateFile;
},
templateGetSuccess: function(templateId, callback) {
return function(response) {
infuser.store.storeTemplate(templateId, response);
callback(infuser.store.getTemplate(templateId));
};
},
templateGetError: function(templateId, templatePath, callback) {
return function(exception) {
if($.inArray(templateId, errors) === -1) {
errors.push(templateId);
}
var templateHtml = returnErrorTemplate("HTTP Status code: " + exception.status, templateId, templatePath);
infuser.store.storeTemplate(templateId, templateHtml);
callback(infuser.store.getTemplate(templateId));
};
},
getAjaxOptions: function(templateOptions) {
}
},
infuserOptions = ['target','loadingTemplate','postRender','preRender','render','bindingInstruction','useLoadingTemplate','model','templateUrl','templateSuffix','templatePrefix',''];
var infuser = {
storageOptions: {
hash: hashStorage,
script: scriptStorage
},
store: hashStorage,
defaults: {
// Template name conventions
templateUrl: "",
templateSuffix: ".html",
templatePrefix: "",
// AJAX Options
ajax: {
"async": true,
"dataType": "html",
"type": "GET"
},
// infuse() specific options - NOT used for "get" or "getSync"
target: function(templateId) { return "#" + templateId; }, // DEFAULT MAPPING
loadingTemplate: {
content: '<div class="infuser-loading">Loading...</div>',
transitionIn: function(target, content) {
var tgt = $(target);
tgt.hide();
tgt.html(content);
tgt.fadeIn();
},
transitionOut: function(target) {
$(target).html("");
}
},
postRender: function(targetElement) { }, // NO_OP effectively by default
preRender: function(targetElement, template) { }, // NO_OP effectively by default
render: function(target, template) {
var tgt = $(target);
if(tgt.children().length === 0) {
tgt.append($(template));
}
else {
tgt.children().replaceWith($(template));
}
},
bindingInstruction: function(template, model) { return template; }, // NO_OP effectively by default
useLoadingTemplate: true // true/false
},
get: function(options, callback) {
var templateOptions = $.extend({}, infuser.defaults, (typeof options === "object" ? options : { templateId: options })),
template;
templateOptions.ajax.url = helpers.getTemplatePath(templateOptions);
template = infuser.store.getTemplate(templateOptions.ajax.url);
if(!template || $.inArray(templateOptions.ajax.url, errors) !== -1) {
templateOptions.ajax.success = helpers.templateGetSuccess(templateOptions.ajax.url, callback);
templateOptions.ajax.error = helpers.templateGetError(templateOptions.templateId, templateOptions.ajax.url, callback);
$.trafficCop(templateOptions.ajax);
}
else {
callback(template);
}
},
getSync: function(options) {
var templateOptions = $.extend({}, infuser.defaults, (typeof options === "object" ? options : { templateId: options }), { ajax: { async: false } }),
template,
templateHtml;
templateOptions.ajax.url = helpers.getTemplatePath(templateOptions);
template = infuser.store.getTemplate(templateOptions.ajax.url);
if(!template || $.inArray(templateOptions.ajax.url, errors) !== -1) {
templateHtml = null;
templateOptions.ajax.success = function(response) { templateHtml = response; };
templateOptions.ajax.error = function(exception) {
if($.inArray(templateOptions.ajax.url) === -1) {
errors.push(templateOptions.ajax.url);
}
templateHtml = returnErrorTemplate("HTTP Status code: exception.status", templateOptions.templateId, templateOptions.ajax.url);
};
$.ajax(templateOptions.ajax);
if(templateHtml === null) {
templateHtml = returnErrorTemplate("An unknown error occurred.", templateOptions.templateId, templateOptions.ajax.url);
}
else {
infuser.store.storeTemplate(templateOptions.ajax.url, templateHtml);
template = infuser.store.getTemplate(templateOptions.ajax.url);
}
}
return template;
},
infuse: function(templateId, renderOptions) {
var templateOptions = $.extend({}, infuser.defaults, (typeof templateId === "object" ? templateId : renderOptions), (typeof templateId === "string" ? { templateId: templateId } : undefined )),
targetElement = typeof templateOptions.target === 'function' ? templateOptions.target(templateId) : templateOptions.target;
if(templateOptions.useLoadingTemplate) {
templateOptions.loadingTemplate.transitionIn(targetElement, templateOptions.loadingTemplate.content);
}
infuser.get(templateOptions, function(template) {
var _template = template;
templateOptions.preRender(targetElement, _template);
_template = templateOptions.bindingInstruction(_template, templateOptions.model);
if(templateOptions.useLoadingTemplate) {
templateOptions.loadingTemplate.transitionOut(targetElement);
}
templateOptions.render(targetElement, _template);
templateOptions.postRender(targetElement);
});
}
};
global.infuser = infuser; })(jQuery, window);
// Knockout External Template Engine
// Author: Jim Cowart
// License: MIT (http://www.opensource.org/licenses/mit-license)
// Version 2.0.5
(function ( global, ko, jQuery, infuser, undefined ) {
var ExternalTemplateSource = function(templateId, options) {
var self = this, origAfterRender;
self.templateId = templateId;
self.loaded = false;
self.template = ko.observable(infuser.defaults.useLoadingTemplate ? infuser.defaults.loadingTemplate.content : undefined);
self.template.data = {};
self.options = ko.utils.extend({},options);
self.options.templateId = templateId;
if(self.options && self.options.afterRender) {
origAfterRender = self.options.afterRender;
self.options.afterRender = function() {
if(self.loaded) {
origAfterRender.apply(self.options, arguments);
}
};
}
};
ko.utils.extend(ExternalTemplateSource.prototype, {
data: function(key, value) {
if (arguments.length === 1) {
if(key === "precompiled") {
this.template();
}
return this.template.data[key];
}
this.template.data[key] = value;
},
text: function(value) {
if (!this.loaded) {
this.getTemplate();
}
if (arguments.length === 0) {
return this.template();
} else {
this.template(arguments[0]);
}
},
getTemplate: function() {
var self = this;
infuser.get(self.options, function(tmpl) {
self.data("precompiled",null);
self.template(tmpl);
self.loaded = true;
});
}
});
var KoExternalTemplateEngine = function(koEngineType) {
var engine = koEngineType ? new koEngineType() : new ko.nativeTemplateEngine();
engine.templates = {};
engine.makeTemplateSource = function(template, bindingContext, options) {
// Named template
if (typeof template == "string") {
var elem = document.getElementById(template);
if (elem)
return new ko.templateSources.domElement(elem);
else {
if(!engine.templates[template]) {
engine.templates[template] = new ExternalTemplateSource(template, options);
}
return engine.templates[template];
}
}
else if ((template.nodeType == 1) || (template.nodeType == 8)) {
// Anonymous template
return new ko.templateSources.anonymousTemplate(template);
}
};
engine.renderTemplate = function (template, bindingContext, options) {
var templateSource = engine.makeTemplateSource(template, bindingContext, options);
return engine.renderTemplateSource(templateSource, bindingContext, options);
};
return engine;
};
ko.KoExternalTemplateEngine = KoExternalTemplateEngine;
if (jQuery.tmpl && jQuery.tmpl.tag.tmpl.open.toString().indexOf('__') >= 0) {
ko.setTemplateEngine(new KoExternalTemplateEngine(ko.jqueryTmplTemplateEngine));
}
else {
ko.setTemplateEngine(new KoExternalTemplateEngine());
}
})( window, ko, jQuery, infuser );

View File

@ -1,51 +1,152 @@
"use strict";
var vm = (function () {
var catalog = ko.observableArray([
Product(1, "T-Shirt", 10.0, 20),
Product(2, "Trousers", 20.0, 10),
Product(3, "Shirt", 15.0, 20),
Product(4, "Shorts", 5.0, 10),
]);
"use strict";
var newProduct = Product("", "", "", "");
var clearNewProduct = function () {
newProduct.name("");
newProduct.price("");
newProduct.stock("");
};
var visibleCatalog = ko.observable(true);
var visibleCart = ko.observable(false);
var addProduct = function (context) {
var id = new Date().valueOf(); // random id from time
var catalog = ko.observableArray([
Product(1, "T-Shirt", 10.0, 20),
Product(2, "Trousers", 20.0, 10),
Product(3, "Shirt", 15.0, 20),
Product(4, "Shorts", 5.0, 10),
]);
var product = Product(id, context.name(), context.price(), context.stock());
catalog.push(product);
clearNewProduct();
};
var newProduct = Product("", "", "", "");
var clearNewProduct = function () {
newProduct.name("");
newProduct.price("");
newProduct.stock("");
};
var searchTerm = ko.observable("");
var filteredCatalog = ko.computed(function () {
// if catalog is empty return empty array
if (!catalog()) {
return [];
}
var filter = searchTerm().toLowerCase();
// if filter is empty return all the catalog
if (!filter) {
return catalog();
}
//filter data
var filtered = ko.utils.arrayFilter(catalog(), function (item) {
var strProp = ko.unwrap(item["name"]).toLocaleLowerCase();
return (strProp.indexOf(filter) > -1);
var addProduct = function (context) {
var id = new Date().valueOf(); // random id from time
var product = Product(
id,
context.name(),
context.price(),
context.stock()
);
catalog.push(product);
clearNewProduct();
$('#addToCatalogModal').modal('hide');
};
var searchTerm = ko.observable("");
var filteredCatalog = ko.computed(function () {
// if catalog is empty return empty array
if (!catalog()) {
return [];
}
var filter = searchTerm().toLowerCase();
// if filter is empty return all the catalog
if (!filter) {
return catalog();
}
//filter data
var filtered = ko.utils.arrayFilter(catalog(), function (item) {
var strProp = ko.unwrap(item["name"]).toLocaleLowerCase();
return strProp.indexOf(filter) > -1;
});
return filtered;
});
return filtered;
});
return {
searchTerm: searchTerm,
catalog: filteredCatalog,
newProduct: newProduct,
addProduct: addProduct,
};
var cart = ko.observableArray([]);
var showCartDetails = function () {
if (cart().length > 0) {
visibleCart(true);
}
};
var addToCart = function (data) {
var item = null;
var tmpCart = cart();
var n = tmpCart.length;
while (n--) {
if (tmpCart[n].product.id() === data.id()) {
item = tmpCart[n];
}
}
if (item) {
item.addUnit();
} else {
item = new CartProduct(data, 0);
item.addUnit();
tmpCart.push(item);
}
cart(tmpCart);
};
// The cart-widget template
var totalItems = ko.computed(function () {
var tmpCart = cart();
var total = 0;
tmpCart.forEach(function (item) {
total += parseInt(item.units(), 10);
});
return total;
});
var grandTotal = ko.computed(function () {
var tmpCart = cart();
var total = 0;
tmpCart.forEach(function (item) {
total += item.units() * item.product.price();
});
return total;
});
// The cart-item template
var removeFromCart = function (data) {
var units = data.units();
var stock = data.product.stock();
data.product.stock(units + stock);
cart.remove(data);
};
// The cart template
var hideCartDetails = function () {
visibleCart(false);
};
var showOrder = function () {
visibleCatalog(false);
};
// The order template
var showCatalog = function () {
visibleCatalog(true);
};
var finishOrder = function () {
cart([]);
hideCartDetails();
showCatalog();
$("#finishOrderModal").modal("show");
};
return {
// first chapter
searchTerm: searchTerm,
catalog: filteredCatalog,
newProduct: newProduct,
addProduct: addProduct,
// second chapter
cart: cart,
showCartDetails: showCartDetails,
addToCart: addToCart,
totalItems: totalItems,
grandTotal: grandTotal,
removeFromCart: removeFromCart,
hideCartDetails: hideCartDetails,
showOrder: showOrder,
showCatalog: showCatalog,
finishOrder: finishOrder,
visibleCatalog: visibleCatalog,
visibleCart: visibleCart,
};
})();
ko.applyBindings(vm);
// ko External Template Settings
infuser.defaults.templateSuffix = ".html";
infuser.defaults.templateUrl = "views";
ko.applyBindings(vm);

View File

@ -0,0 +1,74 @@
<div class="modal fade" id="addToCatalogModal">
<div class="modal-dialog">
<div class="modal-content">
<form
class="form-horizontal"
role="form"
data-bind="with:newProduct"
>
<div class="modal-header">
<button
type="button"
class="close"
data-dismiss="modal"
>
<span aria-hidden="true">&times;</span>
<span class="sr-only">Close</span>
</button>
<h3>Add New Product to the Catalog</h3>
</div>
<div class="modal-body">
<div class="form-group">
<div class="col-sm-12">
<input
type="text"
class="form-control"
placeholder="Name"
data-bind="textInput:name"
/>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<input
type="text"
class="form-control"
placeholder="Price"
data-bind="textInput:price"
/>
</div>
</div>
<div class="form-group">
<div class="col-sm-12">
<input
type="text"
class="form-control"
placeholder="Stock"
data-bind="textInput:stock"
/>
</div>
</div>
</div>
<div class="modal-footer">
<div class="form-group">
<div class="col-sm-12">
<button
type="submit"
class="btn btn-default"
data-bind="{click:$parent.addProduct}"
>
<i
class="glyphicon glyphicon-plus-sign"
></i>
Add Product
</button>
</div>
</div>
</div>
</form>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-diaglog -->
</div>
<!-- /.modal -->

View File

@ -0,0 +1,34 @@
<div class="list-group-item" style="overflow: hidden">
<button
type="button"
class="close pull-right"
data-bind="click:$root.removeFromCart"
>
<span>&times;</span>
</button>
<h4 class="" data-bind="text:product.name"></h4>
<div class="input-group cart-unit">
<input
type="text"
class="form-control"
data-bind="textInput:units"
readonly
/>
<span class="input-group-addon">
<div class="btn-group-vertical">
<button
class="btn btn-default btn-xs"
data-bind="click:addUnit"
>
<i class="glyphicon glyphicon-chevron-up"></i>
</button>
<button
class="btn btn-default btn-xs"
data-bind="click:removeUnit"
>
<i class="glyphicon glyphicon-chevron-down"></i>
</button>
</div>
</span>
</div>
</div>

View File

@ -0,0 +1,2 @@
Total Items: <span data-bind="text:totalItems"></span> Price:
<span data-bind="text:grandTotal"></span>

View File

@ -0,0 +1,16 @@
<button
type="button"
class="close pull-right"
data-bind="click:hideCartDetails"
>
<span>&times;</span>
</button>
<h1>Cart</h1>
<div
data-bind="template: {name: 'cart-item', foreach:cart}"
class="list-group"
></div>
<div data-bind="template: {name: 'cart-widget'}"></div>
<button class="btn btn-primary btn-sm" data-bind="click:showOrder">
Confirm Order
</button>

View File

@ -0,0 +1,46 @@
<div class="input-group">
<span class="input-group-addon">
<i class="glyphicon glyphicon-search"></i> Search
</span>
<input
type="text"
class="form-control"
data-bind="textInput:searchTerm"
/>
</div>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Stock</th>
<th></th>
</tr>
</thead>
<tbody data-bind="foreach:catalog">
<tr data-bind="style:lineColor">
<td data-bind="text:name"></td>
<td data-bind="text:price"></td>
<td data-bind="text:stock"></td>
<td>
<button
class="btn btn-primary"
data-bind="click:$parent.addToCart"
>
<i class="glyphicon glyphicon-plus-sign"></i> Add
</button>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3">
<strong>Items:</strong
><span data-bind="text:catalog().length"></span>
</td>
<td colspan="1">
<span data-bind="template:{name: 'cart-widget'}"></span>
</td>
</tr>
</tfoot>
</table>

View File

@ -0,0 +1,22 @@
<div class="modal fade" id="finishOrderModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-body">
<h2>Your order has been completed!</h2>
</div>
<div class="modal-footer">
<div class="form-group">
<div class="col-sm-12">
<button
type="submit"
class="btn btn-success"
data-dismiss="modal"
>
Continue Shopping
</button>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,16 @@
<h1>Catalog</h1>
<button
class="btn btn-primary btn-sm"
data-toggle="modal"
data-target="#addToCatalogModal"
>
Add New Product
</button>
<button
class="btn btn-primary btn-sm"
data-bind="click: showCartDetails, css:{ disabled: cart().length < 1}"
>
Show Cart Details
</button>
<hr />

View File

@ -0,0 +1,34 @@
<div class="col-xs-12">
<button class="btn btn-sm btn-primary" data-bind="click:showCatalog">
Back to catalog
</button>
<button class="btn btn-sm btn-primary" data-bind="click:finishOrder">
Buy & finish
</button>
</div>
<div class="col-xs-6">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Price</th>
<th>Units</th>
<th>Subtotal</th>
</tr>
</thead>
<tbody data-bind="foreach:cart">
<tr>
<td data-bind="text:product.name"></td>
<td data-bind="text:product.price"></td>
<td data-bind="text:units"></td>
<td data-bind="text:subtotal"></td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3"></td>
<td>Total: <span data-bind="text:grandTotal"></span></td>
</tr>
</tfoot>
</table>
</div>