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.
Related
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>
first of all Im using Knockout js.
So I have a table that I can add and remove rows from it dynamically, my problem is that I want to add a click-to-edit in the table for each row but it doesn't work. once I add a second row Im not enable to edit. Here is my code, you can just copy and past it JSFiddle and it will explain further what Im saying.
Here is my code:
(function () {
var ViewModel = function () {
var self = this;
//Empty Row
self.items = ko.observableArray([]);
self.editing = ko.observable(true);
self.edit = function() { this.editing(true) }
self.addRow = function () {
self.items.push(new Item());
};
self.removeRow = function (data) {
self.items.remove(data);
};
}
var Item = function (fname, lname, address) {
var self = this;
self.firstName = ko.observable(fname);
self.lastName = ko.observable(lname);
self.address = ko.observable(address);
};
vm = new ViewModel()
ko.applyBindings(vm);
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" />
<table class="table table-bordered">
<thead class="mbhead">
<tr class="mbrow">
<th>Input</th>
<th>First Name</th>
<th>Last Name</th>
<th>Address</th>
<th>Actions</th>
</tr>
</thead>
<tbody data-bind="foreach: items">
<tr>
<td>
<select class="form-control common-input-text" data-bind="event: { change: $root.addNewItem }">
<option value="">One</option>
<option value="">Two</option>
<option value="">Three</option>
</select>
</td>
<td>
<b data-bind="uniqueName: true,visible: !($parent.editing()), text: firstName, click: function() { $parent.editing(true) }"></b>
<input data-bind="uniqueName: true, visible: $parent.editing, value: firstName, hasFocus: $parent.editing" />
</td>
<td><span class="input-small" data-bind="value: lastName" /></td>
<td><span class="input-small" data-bind="value: address" /></td>
<td>
<input type="button" value="Remove Row" data-bind="click: $parent.removeRow" class="btn btn-danger" />
</td>
</tr>
</tbody>
</table>
<input type="button" value="Add Row" class="btn btn-primary" data-bind="click: addRow" />
thank you for your help
The problem lies in creating a new row that bounds an observable to hasFocus:
<input data-bind="uniqueName: true,
visible: $parent.editing,
value: firstName,
hasFocus: $parent.editing" /> <-- the problem cause
On row creation, the previously-focused row loses focus, which causes editing to be set to false.
So the solution would be to just use the observable value (instead of bounding the observable itself):
<input data-bind="uniqueName: true,
visible: $parent.editing,
value: firstName,
hasFocus: $parent.editing()" /> // <-- call the observable
But better yet is to add an observable into Item view model, called isFocused, and use it instead:
var Item = function (fname, lname, address) {
var self = this;
self.isFocused = ko.observable(true);
// ... other observables ...
};
<input data-bind="uniqueName: true,
visible: isFocused(),
value: firstName,
hasFocus: isFocused" />
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>
I have some trouble. I write a web app with geo services.
There is a ViewModel thar contains an observableCollection of 'Queuers' and the propery SelectedItem that represent a 'Queue' from that collection. The value of SelectedItem sets from table:
<tbody data-bind="foreach: Queuers">
<tr>
<td class="text-center">
<span data-bind="text: Number"></span>
</td>
<td class="text-center">
<i class="icon-flag icon-2x" data-bind="style: { color: Color }"></i>
</td>
<td class="text-center">
<span data-bind="text: Length"></span>
</td>
<td class="text-center">
<span data-bind="text: Resolution"></span>
</td>
<td class="text-center">
<button style="background: none; border: none" data-bind="click: $root.getData" #*onclick="$('#myModal').modal()"*#>
<i class="icon-thumbs-up-alt icon-2x"></i>
</button>
</td>
<td class="text-center">
<button style="background: none; border: none" data-bind="click: $root.remove">
<i class="icon-trash icon-2x"></i>
</button>
</td>
</tr>
</tbody>
and ViewModel:
var Query = function (number, color, length, res, data) {
this.Number = ko.observable(number);
this.Color = ko.observable(color);
this.Length = ko.observable(length);
this.Resolution = ko.observable(res);
this.Data = ko.observable(data);
};
function TwoDimViewModel() {
var self = this;
self.SelectedItem = ko.observable();
self.SelectedColor = ko.observable(); //just for test
self.Queuers = ko.observableArray();
self.remove = function (q) {
self.Queuers.remove(q);
};
self.getData = function (q) {
self.SelectedItem = q;
self.SelectedColor = q.Color(); //just for test
self.addQ = function (q) {
self.Queuers.push(q);
};
self.removeAll = function () {
self.Queuers([]);
};
}
As you see, there is some logic to manipulate with ObservaleCollection. And all work perfect expect one:
self.getData = function (q) {
self.SelectedItem = q;
}
I want for in my
<div class="row" data-bind="visible: Queuers().length >= 1">
<button class="btn btn-default" onclick="clearAll()">Clear all</button>
<br />
Current selected id: <span data-bind="text: SelectedItem() ? SelectedItem().Number() : 'select element'"></span>
<br />
Выбран цвет: <span data-bind="text: SelectedColor() ? SelectedColor: 'nulll'"></span>
</div>
there will be current value of SelectedElement.
And how to get access to is property (Number, Color, etc...)
Change:
self.SelectedItem = q;
self.SelectedColor = q.Color();
To:
self.SelectedItem(q);
self.SelectedColor(q.Color());
Reference: http://knockoutjs.com/documentation/observables.html#observables
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;
});