jQuery and KnockOutJS linking a slider to the Cart example - javascript

Using the cart example from KnockOutJS.com - how would you link up a slider (or number of sliders), so that the Total Value, takes account of the drop down lists, and the number of additional items (sunglasses in this case) selected in the Slider control.
I have got the Total Value calling the getExtrasTotal() function - when the drop down lists, or number of items is changed - but not when the sliders are changed.
There is a fiddle here for it: http://jsfiddle.net/mtait/mBxky/1/
HTML:
<div class='liveExample'>
<table width='100%'>
<thead>
<tr>
<th width='25%'>Category</th>
<th width='25%'>Product</th>
<th class='price' width='15%'>Price</th>
<th class='quantity' width='10%'>Quantity</th>
<th class='price' width='15%'>Subtotal</th>
<th width='10%'> </th>
</tr>
</thead>
<tbody data-bind='foreach: lines'>
<tr>
<td>
<select data-bind='options: sampleProductCategories, optionsText: "name", optionsCaption: "Select...", value: category'> </select>
</td>
<td data-bind="with: category">
<select data-bind='options: products, optionsText: "name", optionsCaption: "Select...", value: $parent.product'> </select>
</td>
<td class='price' data-bind='with: product'>
<span data-bind='text: formatCurrency(price)'> </span>
</td>
<td class='quantity'>
<input data-bind='visible: product, value: quantity, valueUpdate: "afterkeydown"' />
</td>
<td class='price'>
<span data-bind='visible: product, text: formatCurrency(subtotal())' > </span>
</td>
<td>
<a href='#' data-bind='click: $parent.removeLine'>Remove</a>
</td>
</tr>
</tbody>
</table>
<br />
<label for="slider1">Sunglasses $20 each - how many would you like</label>
<input type="range" class="slide" name="slider1" id="slider1" min="0" max="10" value="0" data-price="20.00" data-id="1">
<br />
<label for="slider2">Doc holder $15 each - how many would you like</label>
<input type="range" class="slide" name="slider2" id="slider2" min="0" max="10" value="0" data-price="15.00" data-id="2">
<p class='grandTotal'>
Total value: <span data-bind='text: formatCurrency(grandTotal())'> </span>
</p>
<button data-bind='click: addLine'>Add product</button>
<button data-bind='click: save'>Submit order</button>
</div>
Javascript:
function formatCurrency(value) {
return "$" + value.toFixed(2);
}
var CartLine = function() {
var self = this;
self.category = ko.observable();
self.product = ko.observable();
self.quantity = ko.observable(1);
self.subtotal = ko.computed(function() {
return self.product() ? self.product().price * parseInt("0" + self.quantity(), 10) : 0;
});
// Whenever the category changes, reset the product selection
self.category.subscribe(function() {
self.product(undefined);
});
};
var Cart = function() {
// Stores an array of lines, and from these, can work out the grandTotal
var self = this;
self.lines = ko.observableArray([new CartLine()]); // Put one line in by default
self.grandTotal = ko.computed(function() {
var total = 0;
$.each(self.lines(), function() { total += this.subtotal() })
var
return total;
});
// Operations
self.addLine = function() { self.lines.push(new CartLine()) };
self.removeLine = function(line) { self.lines.remove(line) };
self.save = function() {
var dataToSave = $.map(self.lines(), function(line) {
return line.product() ? {
productName: line.product().name,
quantity: line.quantity()
} : undefined
});
alert("Could now send this to server: " + JSON.stringify(dataToSave));
};
};
function getExtrasTotal() {
var extrastotal = 0;
var count = 0;
var arr = [];
$('.slide')
.each(function (index, Element) {
var obj = {
id: $(Element).data("id"),
price: $(Element).data("price"),
number: $(Element)
.val()
};
extrastotal += obj.number * obj.price;
});
return extrastotal;
}
ko.applyBindings(new Cart());
Thank you,
Mark

First, you need to make sure that the sliders also updates a ko.observable.
Then, the extrasTotal also need to be a ko.computed like the grandTotal, but observing the extras rather then the cart lines.
I updated your fiddle with some quick-and-dirty:
<input type="range" class="slide" name="slider1" id="slider1" min="0" max="10" data-bind="value: sunglasses">
and
self.extrasTotal = ko.computed(function() {
var sunglasses = self.sunglasses();
var docholders = self.docholders();
return sunglasses * self.sunglassPrice + docholders * self.docholderPrice;
});
Of course, in reality you probably want to make it into an array of possible extras and render the <input>:s with a foreach, and take the price from a database.
This fiddle hints on how to do that:
self.extrasTotal = ko.computed(function() {
var total = 0;
ko.utils.arrayForEach(self.extras(), function (extra) {
var count = extra.count();
total = total + (count * extra.price);
});
return total;
});

Related

Why is my Knockoutjs Computed Observable Not Working when my Observable Array is being Sorted?

Background Info
I have an Observable array with three item details in it. Each item detail has the following properties; Item, Group, TotalQTY and InputQty. When the user enters the Input Quantity and it matches the Total Quantity the item will be "Verified" which means it gets greyed and moves to the bottom of the list. This is done using a JavaScript sort function in the HTML (see snippet below)
<tbody data-bind="foreach: ItemsByGroupArray().sort(function (l, r) { return l.Verified() == r.Verified() ? 0 : (l.Verified() < r.Verified() ? -1 : 1 ) } )">
<tr data-bind="css: {'verified-item': Verified }">
<td data-bind="text: $index() + 1"></td>
<td data-bind="text: ITEM"></td>
<td data-bind="text: GROUP"></td>
<td>
<input type="number" data-bind="value: InputQTY, valueUpdate: ['afterkeydown', 'input']"
size="4" min="0" max="9999" step="1" style=" width:50px; padding:0px; margin:0px">
</td>
<td data-bind="text: TotalQTY"></td>
</tr>
</tbody>
As the array is being sorted there is a computed observable that gets processed. This observable is used to check that if each InputQTY in the ItemsByGroupArray matches the TotalQTY. If it does then the Item Detail gets marked as verified. (see snippet)
self.ITEMInputQTYs = ko.computed(function () {
return ko.utils.arrayForEach(self.ItemsByGroupArray(), function (item) {
if (item.InputQTY() == item.TotalQTY()) {
item.Verified(true);
} else {
item.Verified(false);
}
});
});
Executable Code in which the Snippets Came:
var itemDetail = function (item, group, iQty, tQty, verified) {
var self = this;
self.ITEM = ko.observable(item);
self.GROUP = ko.observable(group);
self.InputQTY = ko.observable(iQty);
self.TotalQTY = ko.observable(tQty);
self.Verified = ko.observable(verified);
};
// The core viewmodel that handles much of the processing logic.
var myViewModel = function () {
var self = this;
self.ItemsByGroupArray = ko.observableArray([]);
self.ITEMInputQTYs = ko.computed(function () {
return ko.utils.arrayForEach(self.ItemsByGroupArray(), function (item) {
if (item.InputQTY() == item.TotalQTY()) {
item.Verified(true);
} else {
item.Verified(false);
}
});
});
self.AddItems = function() {
var newItemData = new itemDetail("ITEM1", "GROUP1", 0, 10, false);
var newItemData2 = new itemDetail("ITEM2", "GROUP1", 0, 10, false);
var newItemData3 = new itemDetail("ITEM3", "GROUP1", 0, 10, false);
self.ItemsByGroupArray.push(newItemData);
self.ItemsByGroupArray.push(newItemData2);
self.ItemsByGroupArray.push(newItemData3);
};
self.AddItems();
};
ko.applyBindings(new myViewModel());
.verified-item
{
text-decoration: line-through;
background: Gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table style="width:90%; margin-left: auto; margin-right: auto;">
<thead>
<tr>
<th></th>
<th>ITEM</th>
<th>GROUP</th>
<th>InputQty</th>
<th>TotalQty</th>
</tr>
</thead>
<!-- <tbody data-bind="foreach: ItemsByGroupArray()"> -->
<tbody data-bind="foreach: ItemsByGroupArray().sort(function (l, r) { return l.Verified() == r.Verified() ? 0 : (l.Verified() < r.Verified() ? -1 : 1 ) } )">
<tr data-bind="css: {'verified-item': Verified }">
<td data-bind="text: $index() + 1"></td>
<td data-bind="text: ITEM"></td>
<td data-bind="text: GROUP"></td>
<td>
<input type="number" data-bind="value: InputQTY, valueUpdate: ['afterkeydown', 'input']"
size="4" min="0" max="9999" step="1" style=" width:50px; padding:0px; margin:0px">
</td>
<td data-bind="text: TotalQTY"></td>
</tr>
</tbody>
</table>
The Problem
The issue here is if you run the fiddle and perform the following steps "Item3" will not get Verified.
Double click InputQTY for Item1 and change it to a value of 10. Result: Item1 gets verified.
Double click InputQTY for Item2 and change it to a value of 10. Result: Item2 does not get verified.
Double click InputQTY for Item3 and change it to a value of 10. Result: Item2 gets verified but Item3 does not.
My Question
Why is the third item not getting computed as expected and how can I fix this? Also, is this a possible bug in Knockoutjs code?
Thanks in advance for any replies!
this works once because of how knockout picks up its initial computed's variables. But what's actually required is to trigger the observableArray whenever a property of an item changes. I have added the additional code in the AddItem section that fixes your issue.
var itemDetail = function(item, group, iQty, tQty, verified) {
var self = this;
self.ITEM = ko.observable(item);
self.GROUP = ko.observable(group);
self.InputQTY = ko.observable(iQty);
self.TotalQTY = ko.observable(tQty);
self.Verified = ko.observable(verified);
};
// The core viewmodel that handles much of the processing logic.
var myViewModel = function() {
var self = this;
self.ItemsByGroupArray = ko.observableArray([]);
self.ITEMInputQTYs = ko.computed(function() {
return ko.utils.arrayForEach(self.ItemsByGroupArray(), function(item) {
if (item.InputQTY() == item.TotalQTY()) {
item.Verified(true);
} else {
item.Verified(false);
}
});
});
self.AddItems = function() {
var newItemData = new itemDetail("ITEM1", "GROUP1", 0, 10, false);
var newItemData2 = new itemDetail("ITEM2", "GROUP1", 0, 10, false);
var newItemData3 = new itemDetail("ITEM3", "GROUP1", 0, 10, false);
self.ItemsByGroupArray.push(newItemData);
self.ItemsByGroupArray.push(newItemData2);
self.ItemsByGroupArray.push(newItemData3);
ko.utils.arrayForEach(self.ItemsByGroupArray(), function(item) {
item.InputQTY.subscribe(function(val) {
self.ItemsByGroupArray.valueHasMutated();
});
});
};
self.AddItems();
};
ko.applyBindings(new myViewModel());
.verified-item {
text-decoration: line-through;
background: Gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table style="width:90%; margin-left: auto; margin-right: auto;">
<thead>
<tr>
<th></th>
<th>ITEM</th>
<th>GROUP</th>
<th>InputQty</th>
<th>TotalQty</th>
</tr>
</thead>
<!-- <tbody data-bind="foreach: ItemsByGroupArray()"> -->
<tbody data-bind="foreach: ItemsByGroupArray().sort(function (l, r) { return l.Verified() == r.Verified() ? 0 : (l.Verified() < r.Verified() ? -1 : 1 ) } )">
<tr data-bind="css: {'verified-item': Verified }">
<td data-bind="text: $index() + 1"></td>
<td data-bind="text: ITEM"></td>
<td data-bind="text: GROUP"></td>
<td>
<input type="number" data-bind="value: InputQTY, valueUpdate: ['afterkeydown', 'input']" size="4" min="0" max="9999" step="1" style=" width:50px; padding:0px; margin:0px">
</td>
<td data-bind="text: TotalQTY"></td>
</tr>
</tbody>
</table>

Combine two Knockout Examples

I am trying to get two examples from knockout.com working and I have yet to figure it out. Any help would be greatly appreciated!
http://jsfiddle.net/gZC5k/2863/
When running this project and using a debugger I get an error:
Uncaught ReferenceError: Unable to process binding "foreach: function (){return linesa }"
Message: linesa is not defined
From the original example I changed lines to linesa to see if anything else was screwing it up. It still did not like linesa
My main goal is to get these two samples working together. The Add a contact button works but the Add product does not work.
Thank you!
<div class='liveExample'>
<h2>Contacts</h2>
<div id='contactsList'>
<table class='contactsEditor'>
<tr>
<th>First name</th>
<th>Last name</th>
<th>Phone numbers</th>
</tr>
<tbody data-bind="foreach: contactsa">
<tr>
<td>
<input data-bind='value: firstName' />
<div><a href='#' data-bind='click: $root.removeContact'>Delete</a></div>
</td>
<td><input data-bind='value: lastName' /></td>
<td>
<table>
<tbody data-bind="foreach: phones">
<tr>
<td><input data-bind='value: type' /></td>
<td><input data-bind='value: number' /></td>
<td><a href='#' data-bind='click: $root.removePhone'>Delete</a></td>
</tr>
</tbody>
</table>
<a href='#' data-bind='click: $root.addPhone'>Add number</a>
</td>
</tr>
</tbody>
</table>
</div>
<p>
<button data-bind='click: addContact'>Add a contact</button>
<button data-bind='click: save, enable: contactsa().length > 0'>Save to JSON</button>
</p>
<textarea data-bind='value: lastSavedJson' rows='5' cols='60' disabled='disabled'> </textarea>
</div>
<div class='liveExample'>
<table width='100%'>
<thead>
<tr>
<th width='25%'>Category</th>
<th width='25%'>Product</th>
<th class='price' width='15%'>Price</th>
<th class='quantity' width='10%'>Quantity</th>
<th class='price' width='15%'>Subtotal</th>
<th width='10%'> </th>
</tr>
</thead>
<tbody data-bind='foreach: linesa'>
<tr>
<td>
<select data-bind='options: sampleProductCategories, optionsText: "name", optionsCaption: "Select...", value: category'> </select>
</td>
<td data-bind="with: category">
<select data-bind='options: products, optionsText: "name", optionsCaption: "Select...", value: $parent.product'> </select>
</td>
<td class='price' data-bind='with: product'>
<span data-bind='text: formatCurrency(price)'> </span>
</td>
<td class='quantity'>
<input data-bind='visible: product, value: quantity, valueUpdate: "afterkeydown"' />
</td>
<td class='price'>
<span data-bind='visible: product, text: formatCurrency(subtotal())' > </span>
</td>
<td>
<a href='#' data-bind='click: $parent.removeLine'>Remove</a>
</td>
</tr>
</tbody>
</table>
<p class='grandTotal'>
Total value: <span data-bind='text: formatCurrency(grandTotal())'> </span>
</p>
<button data-bind='click: addLine'>Add product</button>
<button data-bind='click: save'>Submit order</button>
</div>
var initialData = [
{ firstName: "Danny", lastName: "LaRusso", phones: [
{ type: "Mobile", number: "(555) 121-2121" },
{ type: "Home", number: "(555) 123-4567"}]
},
{ firstName: "Sensei", lastName: "Miyagi", phones: [
{ type: "Mobile", number: "(555) 444-2222" },
{ type: "Home", number: "(555) 999-1212"}]
}
];
var ContactsModel = function(contactstest) {
var self = this;
self.contactsa = ko.observableArray(ko.utils.arrayMap(contactstest, function(contact) {
return { firstName: contact.firstName, lastName: contact.lastName, phones: ko.observableArray(contact.phones) };
}));
self.addContact = function() {
self.contactsa.push({
firstName: "",
lastName: "",
phones: ko.observableArray()
});
};
self.removeContact = function(contact) {
self.contactsa.remove(contact);
};
self.addPhone = function(contact) {
contact.phones.push({
type: "",
number: ""
});
};
self.removePhone = function(phone) {
$.each(self.contactsa(), function() { this.phones.remove(phone) })
};
self.save = function() {
self.lastSavedJson(JSON.stringify(ko.toJS(self.contactsa), null, 2));
};
self.lastSavedJson = ko.observable("")
};
ko.applyBindings(new ContactsModel(initialData));
function formatCurrency(value) {
return "$" + value.toFixed(2);
}
var CartLine = function() {
var self = this;
self.category = ko.observable();
self.product = ko.observable();
self.quantity = ko.observable(1);
self.subtotal = ko.computed(function() {
return self.product() ? self.product().price * parseInt("0" + self.quantity(), 10) : 0;
});
// Whenever the category changes, reset the product selection
self.category.subscribe(function() {
self.product(undefined);
});
};
var Cart = function() {
// Stores an array of lines, and from these, can work out the grandTotal
var self = this;
self.linesa = ko.observableArray([new CartLine()]); // Put one line in by default
self.grandTotal = ko.computed(function() {
var total = 0;
$.each(self.linesa(), function() { total += this.subtotal() })
return total;
});
// Operations
self.addLine = function() { self.linesa.push(new CartLine()) };
self.removeLine = function(line) { self.linesa.remove(line) };
self.save = function() {
var dataToSave = $.map(self.linesa(), function(line) {
return line.product() ? {
productName: line.product().name,
quantity: line.quantity()
} : undefined
});
alert("Could now send this to server: " + JSON.stringify(dataToSave));
};
};
ko.applyBindings(new Cart());
ViewModel is a ContactsModel, since ContactsModel does not have a linesa property, it cannot render the contents of the table.
<tbody data-bind='foreach: linesa'>
this is the offending line.
You are working with 2 ViewModels actually, the Cart ViewModel and the Contacts ViewModel.
you need to make a new ViewModel that implements both ViewModels and you can bind the views using with binding.
Code:
function CombinedViewModel(){
this.cart=new Cart();
this.contact=new Contact();
}
ko.appyBindings(new CombinedViewModel());
and for the bindings
<div data-bind="with:cart"><!-- cart html view goes here --></div>
<div data-bind="with:contact"><!-- contact html view goes here --></div>

Get value from selected Table row Knockout.js

I need a little help.
I have created a table that gets values from JSON response, but for this example lets create a hardcoded html table like following:
<table id="devtable">
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
</tr>
<tr>
<td>001</td>
<td>Jhon</td>
<td>Single</td>
</tr>
<tr>
<td>002</td>
<td>Mike</td>
<td>Married</td>
</tr>
<tr>
<td>003</td>
<td>Marrie</td>
<td>Complicated</td>
</tr>
</table>
ID : <input type="text" name="ID" data-bind="value: ID" disabled/>
<br>
Name : <input type="text" name="Name" data-bind="value: Name" disabled/>
<br>
Status : <input type="text" name="Status" data-bind="value: Status" disabled/>
<br>
<input type="button" value="Send" disabled/>
Requirement is: when I select a row of table, values of columns goes to the input boxes and enable button as well. As I am trying to learn Knockout.js by doing this exercise. I think I have to make a viewmodel like this:
var rowModel = function (id, name, status) {
this.ID = ko.observable(id);
this.Name = ko.observable(name);
this.Status = ko.observable(status);
}
Link of project is here: http://jsfiddle.net/qWmat/
Here's an example of how you could do it:
http://jsfiddle.net/qWmat/3/
function MyVM(data) {
var self = this;
self.items = ko.observableArray(data.map(function (i) {
return new rowModel(i.id, i.name, i.status);
}));
self.select = function(item) {
self.selected(item);
};
self.selected = ko.observable(self.items()[0]);
}
And you bind your textboxes to the properties in the selected property:
<input type="text" name="ID" data-bind="value: selected().ID" disabled/>
And you bind the click handler in your tr like so:
<tr data-bind="click: $parent.select">
Updated to include enable binding (http://jsfiddle.net/qWmat/8/). Add a property for whether or not to edit:
self.enableEdit = ko.observable(false);
Then update your select function to turn it to true:
self.select = function(item) {
self.selected(item);
self.enableEdit(true);
};
If / when you save or cancel you could the set it back to false if you want.
Update your bindings on the input boxes:
<input type="text" name="Status" data-bind="value: selected().Status, enable: enableEdit" />
I've created a demo for you, but to know how it works, you should investigate knockout documentation.
ViewModel:
<table id="devtable">
<thead>
<tr>
<th>ID</th>
<th>Name</th>
<th>Status</th>
</tr>
</thead>
<tbody data-bind="foreach: Items" >
<tr data-bind='click: $parent.setEditItem'>
<td data-bind="text: ID"></td>
<td data-bind="text: Name"></td>
<td data-bind="text: Status"></td>
</tr>
</tbody>
</table>
<!-- ko with: SelectedItem -->
ID :
<input type="text" name="ID" data-bind="value: ID, attr: {disabled: !$parent.IsEditMode()}" />
<br>Name :
<input type="text" name="Name" data-bind="value: Name, attr: {disabled: !$parent.IsEditMode()}"/>
<br>Status :
<input type="text" name="Status" data-bind="value: Status, attr: {disabled: !$parent.IsEditMode()}"/>
<br>
<input type="button" value="Send" data-bind="attr: {disabled: !$parent.IsEditMode()}"/>
<!-- /ko -->
Html:
function ItemModel(id, name, status) {
var self = this;
self.ID = ko.observable(id);
self.Name = ko.observable(name);
self.Status = ko.observable(status);
}
function ViewModel() {
var self = this;
self.Items = ko.observableArray([
new ItemModel('001', 'Jhon', 'Single'),
new ItemModel('002', 'Mike', 'Married'),
new ItemModel('003', 'Marrie', 'Complicated')
]);
self.SelectedItem = ko.observable(new ItemModel());
self.IsEditMode = ko.observable();
self.setEditItem = function(item) {
self.SelectedItem(item);
self.IsEditMode(true);
}
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
Demo

Sum of an checkbox list of items

I have a checkbox list of items. I want everytime I check items, to be able to display the price of the item and the sales tax for it, sum a subtotal of each value (price and tax) and sum the total cost. This is what I've done so far (the code is a mix from scripts I' ve found online):
<html>
<head>
<title>List</title>
<SCRIPT>
function UpdateCost() {
var sum = 0;
var gn, elem;
for (i=1; i<3; i++) {
gn = 'item'+i;
elem = document.getElementById(gn);
if (elem.checked == true) { sum += Number(elem.value);
}
}
document.getElementById('totalcost').value = sum.toFixed(2);
}
</SCRIPT>
</head>
<body>
<FORM >
<table border="1px" align="center">
<tr>
<td>List of Items
<td>Price
<td>Tax
</tr>
<tr>
<td><input type="checkbox" id='item1' value="10.00" onclick="UpdateCost()">item1
<td><INPUT TYPE="text" id='price1' SIZE=5 value="">
<td><INPUT TYPE="text" id='tax1' SIZE=5 value="">
</tr>
<tr>
<td><input type="checkbox" id='item2' value="15.00" onclick="UpdateCost()">item2
<td><INPUT TYPE="text" id='price2' SIZE=5 value="">
<td><INPUT TYPE="text" id='tax2' SIZE=5 value="">
</tr>
<TR>
<TD>Subtotals
<TD><INPUT TYPE="text" id="subtotal1" value="" SIZE=5>
<TD><INPUT TYPE="text" id="subtotal2" value="" SIZE=5>
</TR>
<tr>
<td>Total Cost:
<td><input type="text" id="totalcost" value="" SIZE=5>
<td><input type="reset" value="Reset">
</tr>
</table>
</FORM>
</body>
</html>
Here is a working implementation using Knockout.js. The fiddle is here: http://jsfiddle.net/pJ5Z7/.
The ViewModel and Item functions define your data structure and logic. Bindings to properties in the view-model are done in the HTML and Knockout will update those dynamically. These are two-way: I left the price values as inputs to illustrate this. If you check an item and change its price, you will see that change reflected in the rest of the model and view (after the input loses focus).
This approach allows for clean separation of concerns and much more maintainable code. Declarative bindings in Knockout and similar libraries help you avoid manual DOM manipulation as well.
If you want to change your dataset, all you have to do is add or remove items in the initialization code:
var items = [
new Item('item1', 10.00),
new Item('item2', 15.00)
];
With the old approach, you would have had to update the DOM as well as all of your logic. This data could even be loaded dynamically from a web service or anywhere else.
I also cleaned up the markup a bit and moved the size definition of input elements to CSS. It's better practice to define styles there.
If you want to learn more, just go to the Knockout website. There are a number of helpful demonstrations and tutorials.
JavaScript
//Main viewModel
function ViewModel(items) {
var self = this;
self.items = ko.observableArray(items);
self.priceSubtotal = ko.computed(function() {
var i = 0;
var items = self.items();
var sum = 0;
for(i = 0; i < items.length; i++) {
//Only add up selected items
items[i].selected() && (sum += parseFloat(items[i].price()));
}
return sum.toFixed(2);
});
self.taxSubtotal = ko.computed(function() {
var i = 0;
var items = self.items();
var sum = 0;
for(i = 0; i < items.length; i++) {
//Only add up selected items
items[i].selected() && (sum += parseFloat(items[i].taxAmount()));
}
return sum.toFixed(2);
});
self.totalCost = ko.computed(function() {
return (parseFloat(self.priceSubtotal()) + parseFloat(self.taxSubtotal())).toFixed(2);
});
//Functions
self.reset = function() {
var i = 0;
var items = self.items();
var sum = 0;
for(i = 0; i < items.length; i++) {
items[i].selected(false);
}
};
}
//Individual items
function Item(name, price) {
var self = this;
self.name = ko.observable(name);
self.price = ko.observable(price);
self.selected = ko.observable(false);
self.taxRate = ko.observable(0.06);
self.taxAmount = ko.computed(function() {
return (self.price() * self.taxRate()).toFixed(2);
});
}
//Initialization with data- this could come from anywhere
var items = [
new Item('item1', 10.00),
new Item('item2', 15.00)
];
//Apply the bindings
ko.applyBindings(new ViewModel(items));
HTML
<form>
<table border="1px" align="center">
<tr>
<td>List of Items</td>
<td>Price</td>
<td>Tax</td>
</tr>
<!-- ko foreach: items -->
<tr>
<td>
<input type="checkbox" data-bind="checked: selected" />
<span data-bind="text: name"></span>
</td>
<td>
<input type="text" data-bind="value: price"/>
</td>
<td>
<span data-bind="text: selected() ? taxAmount() : ''"></span>
</td>
</tr>
<!-- /ko -->
<tr>
<td>Subtotals</td>
<td>
<span data-bind="text: priceSubtotal"></span>
</td>
<td>
<span data-bind="text: taxSubtotal"></span>
</td>
</tr>
<tr>
<td>Total Cost:</td>
<td>
<span data-bind="text: totalCost"></span>
</td>
<td>
<input type="button" value="Reset" data-bind="click: reset" />
</td>
</tr>
</table>
</form>

options single use value from json - Knockout JS

I have data coming in from another view model and I'm displaying it in a dropdown menu. I need each option from the other view model to be unique per line, so once the user adds an item to the list, that option isn't available anymore.
Here is the working fiddle: http://jsfiddle.net/QTUqD/9/
window.usrViewModel = new function () {
var self = this;
window.viewModel = self;
self.list = ko.observableArray();
self.pageSize = ko.observable(10);
self.pageIndex = ko.observable(0);
self.selectedItem = ko.observable();
self.extData = ko.observableArray();
extData = ExtListViewModel.list();
self.edit = function (item) {
if($('#usrForm').valid()) {
self.selectedItem(item);
}
};
self.cancel = function () {
self.selectedItem(null);
};
self.add = function () {
if($('#usrForm').valid()) {
var newItem = new Users();
self.list.push(newItem);
self.selectedItem(newItem);
self.moveToPage(self.maxPageIndex());
};
};
self.remove = function (item) {
if (confirm('Are you sure you wish to delete this item?')) {
self.list.remove(item);
if (self.pageIndex() > self.maxPageIndex()) {
self.moveToPage(self.maxPageIndex());
}
}
$('.error').hide();
};
self.save = function () {
self.selectedItem(null);
};
self.templateToUse = function (item) {
return self.selectedItem() === item ? 'editUsrs' : 'usrItems';
};
self.pagedList = ko.dependentObservable(function () {
var size = self.pageSize();
var start = self.pageIndex() * size;
return self.list.slice(start, start + size);
});
self.maxPageIndex = ko.dependentObservable(function () {
return Math.ceil(self.list().length / self.pageSize()) - 1;
});
self.previousPage = function () {
if (self.pageIndex() > 0) {
self.pageIndex(self.pageIndex() - 1);
}
};
self.nextPage = function () {
if (self.pageIndex() < self.maxPageIndex()) {
self.pageIndex(self.pageIndex() + 1);
}
};
self.allPages = ko.dependentObservable(function () {
var pages = [];
for (i = 0; i <= self.maxPageIndex() ; i++) {
pages.push({ pageNumber: (i + 1) });
}
return pages;
});
self.moveToPage = function (index) {
self.pageIndex(index);
};
};
ko.applyBindings(usrViewModel, document.getElementById('usrForm'));
function Users(fname, lname, email, phone, access, usrExtVal){
this.fname = ko.observable(fname);
this.lname = ko.observable(lname);
this.email = ko.observable(email);
this.phone = ko.observable(phone);
this.access = ko.observable(access);
this.usrExtVal = ko.observableArray(usrExtVal);
}
<form id="usrForm">
<h2>Users</h2>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Phone Number</th>
<th>Access</th>
<th>Extension</th>
<th style="width: 100px; text-align:right;" />
</tr>
</thead>
<tbody data-bind=" template:{name:templateToUse, foreach: pagedList }"></tbody>
</table>
<p class="pull-right"><a class="btn btn-primary" data-bind="click: $root.add" href="#" title="edit"><i class="icon-plus"></i> Add Users</a></p>
<div class="pagination pull-left">
<ul><li data-bind="css: { disabled: pageIndex() === 0 }">Previous</li></ul>
<ul data-bind="foreach: allPages">
<li data-bind="css: { active: $data.pageNumber === ($root.pageIndex() + 1) }"></li>
</ul>
<ul><li data-bind="css: { disabled: pageIndex() === maxPageIndex() }">Next</li></ul>
</div>
<br clear="all" />
<script id="usrItems" type="text/html">
<tr>
<td data-bind="text: fname"></td>
<td data-bind="text: lname"></td>
<td data-bind="text: email"></td>
<td data-bind="text: phone"></td>
<td data-bind="text: access"></td>
<td data-bind="text: usrExtVal"></td>
<td class="buttons">
<a class="btn" data-bind="click: $root.edit" href="#" title="edit"><i class="icon-edit"></i></a>
<a class="btn" data-bind="click: $root.remove" href="#" title="remove"><i class="icon-remove"></i></a>
</td>
</tr>
</script>
<script id="editUsrs" type="text/html">
<tr>
<td><input data-errorposition="b" class="required" name="fname" data-bind="value: fname" /></td>
<td><input data-errorposition="b" class="required" name="lname" data-bind="value: lname" /></td>
<td><input data-errorposition="b" class="required" name="email" data-bind="value: email" /></td>
<td><input data-errorposition="b" class="required" name="phone" data-bind="value: phone" /></td>
<td><select data-bind="value: access"><option>Employee</option><option>Administrator</option><option>PBX Admin</option><option>Billing</option></select></td>
<td><select data-bind="options: $root.extOptions, optionsText: 'extension', value: usrExtVal"></select></td>
<td class="buttons">
<a class="btn btn-success" data-bind="click: $root.save" href="#" title="save"><i class="icon-ok"></i></a>
<a class="btn" data-bind="click: $root.remove" href="#" title="remove"><i class="icon-remove"></i></a>
</td>
</tr>
</script>
</form>
I'm not sure if this is what you are looking for, but here is a fiddle that computes the available values based on the those already in use:
http://jsfiddle.net/jearles/QTUqD/11/
--
HTML
<select data-bind="options: $root.availableExtData, optionsText: 'extension', value: usrExtVal"></select>
JS
self.availableExtData = ko.computed(function() {
var inUse = [];
if (!self.selectedItem()) return inUse;
ko.utils.arrayForEach(self.list(), function(item) {
if (inUse.indexOf(item.usrExtVal().extension) == -1 && self.selectedItem() != item) inUse.push(item.usrExtVal().extension);
});
return ko.utils.arrayFilter(self.extData(), function(item) {
return inUse.indexOf(item.extension) == -1;
});
});
--
The code ensures that when the select item is being edited that it's current value is available. Additionally I also don't show the add button once there is a row for all available values.
Make a computed observable with the available options, that filters away from the complete list (extData) those that have already been assigned to an item - except the one used by the selected item - and then bind the options of the select field to that computed instead of the full list.

Categories

Resources