Knockout js. Get element from table and set it as current. - javascript

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

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>

KnockoutJS: Making sure items are removing before adding new ones

I'm running into an issue where ever so often my data isn't being updated properly in my model. I've create a fiddle for it here: https://jsfiddle.net/wzhv5ghy/
In the code all the updates to the model are handled by this function:
self.updateData = function (data) {
self.deliveryItems.removeAll(); // Sometimes our data won't replace correctly so lets kill everything here and then add it from scratch
if ( data != null )
self.deliveryItems(data);
self.showLoadingNotice(false);
// let the sorting plugin know that we made a update
$("#deliveryItemsTable").trigger("update");
//self.showTable(true);
//console.log(data);
// We moved this to functions.js since we use it multiple times
//setupDeliveryItemsTable();
};
I put it in a single function to make it easier when updating the code using various JSON calls.
The issue thats happening is that every so often the page will show old data (say 1000 lines) in the table while the following code will display 1 as the count of the models data:
<span data-bind="text: deliveryItems().length"></span>
My guess is that this line:
self.deliveryItems.removeAll();
is not running before this line
self.deliveryItems(data);
How can I make sure that all items are removed from the model and table before the model is updated with the fresh data?
Like #f_martinez pointed out our issue was with the jQuery Tablesorter plugin and KO fighting to update the same table. To get around this I ended up using the code below to switch to a KO tablesorting function. The sorting seems to be slower than the Tablesorter version but it's better than having bad data.
/*-------------------------------------------------------------------------*/
// Knockout.js Functions
/*-------------------------------------------------------------------------*/
ko.bindingHandlers.sort = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var asc = false;
element.style.cursor = 'pointer';
//console.log(element);
element.onclick = function(){
var value = valueAccessor();
var prop = value.prop;
var data = value.arr;
asc = !asc;
data.sort(function(left, right){
var rec1 = left;
var rec2 = right;
if(!asc) {
rec1 = right;
rec2 = left;
}
var props = prop.split('.');
for(var i in props){
var propName = props[i];
var parenIndex = propName.indexOf('()');
if(parenIndex > 0){
propName = propName.substring(0, parenIndex);
rec1 = rec1[propName]();
rec2 = rec2[propName]();
} else {
rec1 = rec1[propName];
rec2 = rec2[propName];
}
}
return rec1 == rec2 ? 0 : rec1 < rec2 ? -1 : 1;
});
};
}
};
Here's the code for the table itself:
<div class="alert alert-info" data-bind="visible: showLoadingNotice">
Loading...
</div>
<table class="table table-striped table-bordered table-hover" id="deliveryItemsTable" data-bind="visible: showTable">
<thead>
<tr class="title1">
<td colspan="100%">Delivery Items ( <span data-bind="text: deliveryItems().length"></span> )</td>
</tr>
<tr class="title2">
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'created_on' }">Created On</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'company_name' }">Company</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'product_number_company' }">Item Number</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'client_phone_number' }">Phone Number</th>
<th data-bind="sort: { arr: deliveryItems, prop: 'price_to_display' }">Price</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'location' }">Location</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'notes' }">Notes</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'delivered_by_text' }">Delivery Driver</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'status_labels' }">Status</th>
<th class="header nowrap"></th>
</tr>
</thead>
<tbody data-bind="foreach: deliveryItems">
<tr data-bind="attr: {id: 'row_' + id }">
<td data-bind="text: created_on"></td>
<td data-bind="text: company_name"></td>
<td data-bind="text: product_number_company"></td>
<td data-bind="text: client_phone_number"></td>
<td data-bind="text: formatCurrency(price_to_display)"></td>
<td>
<span data-bind="text: location, attr: {id: 'edit-delivery_items-' + id + '_location' }"></span>
<button data-bind="click: $root.editLocation, visible: $root.canEdit" class="btn btn-default btn-xs" title="Edit"><i class="glyphicon glyphicon-edit"></i></button>
</td>
<td>
<span data-bind="text: notes, attr: {id: 'edit-delivery_items-' + id + '_notes' }"></span>
<button data-bind="click: $root.editNotes, visible: $root.canEdit" class="btn btn-default btn-xs" title="Edit"><i class="glyphicon glyphicon-edit"></i></button>
</td>
<td>
<span data-bind="text: delivered_by_text, attr: {id: 'edit_select-delivery_items-' + id + '_delivered_by' }"></span>
<button data-bind="click: $root.editDeliveryDriver, visible: $root.canEdit" class="btn btn-default btn-xs" title="Edit"><i class="glyphicon glyphicon-edit"></i></button>
</td>
<td data-bind="html: status_labels"></td>
<td class="center"><span class="btn-group">
<button data-bind="click: $root.markAsDelivered, visible: $root.canMarkComplete" class="btn btn-default" title="Mark as delivered"><i class="glyphicon glyphicon-check"></i></button>
<a data-bind="attr: {href: 'index.php?action=editDeliveryItem&id=' + id }, visible: $root.canEdit" class="btn btn-default" title="Edit"><i class="glyphicon glyphicon-edit"></i></a>
</span></td>
</tr>
</tbody>
</table>
<div class="alert alert-info" data-bind="visible: showLoadingNotice">
Loading...
</div>
<div id="deliveryItemsTableUpdateNotice"></div>

Knockout nested foreach add and remove not working

I'm having troubles adding to and removing from inside a nested foreach. In this example, we have a House which has many Rooms. Each Room has many pieces of furniture. With this code so far, I can get the data to display properly and can add and remove Rooms, but I cannot add or remove Furniture.
HTML
//Other House fields work as expected above this section
<div data-bind='foreach: rooms'>
<button type="button" data-bind='visible: $root.rooms().length > 1, click: $root.removeRoom'> Remove Room </button>
<p> Room Name</p>
<input type="text" data-bind="value: name"></input>
//with: appears to work the same as a foreach -- neither seem to work
<div data-bind="with: furnitures">
<button type="button" data-bind='click: $root.rooms().furnitures().removeFurniture'> Remove Furniture </button>
<p> Furniture Name</p>
<input type="text" data-bind="value: name"></input>
</div>
<button type="button" data-bind='click: $root.rooms().furnitures().addFurniture'> Add Furniture </button>
</div>
<button type="button" data-bind='click: $root.addRoom'> Add Room </button>
JavaScript
var HouseModel = function(rooms) {
var self = this;
self.rooms = ko.observableArray(rooms);
// Not sure what to put here for Furniture because each room array item has an array of many furnitures
// ROOM MANAGEMENT ==========================
self.addRoom = function() {
self.rooms.push({
name:"",
furnitures[]: ""
});
};
self.removeRoom = function(room) {
self.rooms.remove(room);
};
// FURNITURE MANAGEMENT ==========================
// Not sure where this goes
self.addFurniture = function() {
self.furnitures.push({
name: ""
});
};
self.removeFurniture = function(furniture) {
self.furnitures.remove(furniture);
};
};
var viewModel = new HouseModel(rooms); // rooms are the pre-existing rooms and their furniture, in JSON format
ko.applyBindings(viewModel);
The main problems with this are probably to do with the context of the data-bind of the buttons, and the way the model has been coded. Something is missing or wrong.
Thoughts are appreciated.
UPDATE
This is a fiddle of the problem:
http://jsfiddle.net/zhLf1n61/
Resources:
Knockout nested view model (this example is different because it does not have nested view models)
Knockout.JS official -working with collections (I found this to be difficult to apply to my situation)
Updated javascript...
Main entry point is the HouseModel... Houses have Rooms (and methods for removing adding them) and Rooms have Furniture (with methods for adding and removing them). It's all about encapsulation and scope.
Fiddle here: http://jsfiddle.net/zqwom7kd/
var initialData = [{
"name": "Living Room",
"furnitures": [{
"name": "Bookshelf",
"size": "Medium"
}]
}, {
"name": "Bedroom",
"furnitures": [{
"name": "Bed",
"size": "Large"
}, {
"name": "Night Table",
"size": "Small"
}, {
"name": "Jacuzzi",
"size": "Large"
}]
}];
var Furniture = function(data) {
var self = this;
self.name = ko.observable('');
self.size = ko.observable('');
if (typeof data !== 'undefined') {
self.name(data.name);
self.size(data.size);
}
}
var Room = function(name, furnitures) {
var self = this;
self.name = ko.observable(name);
self.furnitures = ko.observableArray([]);
if (typeof furnitures !== 'undefined') {
$.each(furnitures, function(i, el) {
self.furnitures.push(new Furniture({name: el.name, size: el.size}));
});
}
self.removeFurniture = function(furniture) {
self.furnitures.remove(furniture);
};
self.addFurniture = function() {
console.log("added");
self.furnitures.push(new Furniture({name: '', size: ''}));
};
};
var HouseModel = function (rooms) {
var self = this;
self.save = function() {
console.log("do stuff");
};
self.lastSavedJson = ko.observable('');
self.rooms = ko.observableArray([]);
if (typeof rooms !== 'undefined') {
$.each(rooms, function(i, el) {
self.rooms.push(new Room(el.name, el.furnitures));
});
}
self.addRoom = function(name) {
self.rooms.push(new Room(name));
};
self.removeRoom = function (room) {
self.rooms.remove(room);
};
};
ko.applyBindings(new HouseModel(initialData));
HTML
<h2>House Components</h2>
<div id='roomsList'>
<table class='roomsEditor'>
<tr>
<th>Room Name</th>
<th>Furnitures</th>
</tr>
<tbody data-bind="foreach: rooms">
<tr class="well">
<td valign="top">
<input type="text" data-bind='value: name' />
<div> <button class="btn btn-danger" data-bind='click: $root.rooms.removeRoom'>Remove Room</button>
</div>
</td>
<td>
<table>
<tbody data-bind="foreach: furnitures">
<tr>
<td>
<input type="text" data-bind='value: name' />
</td>
<td>
<input type="text" data-bind='value: size' />
</td>
<td>
<button class="btn btn-danger" data-bind='click: $parent.removeFurniture'>Delete Furniture</button>
</td>
</tr>
</tbody>
</table>
<button class="btn btn-success" data-bind='click: addFurniture'>Add Furniture</button>
</td>
</tr>
</tbody>
</table>
</div>
<p>
<button class="btn btn-success" data-bind='click: $root.rooms.addRoom'>Add Room</button>
<button data-bind='click: save, enable: rooms().length > 0'>Save to JSON</button>
</p>
<textarea data-bind='value: lastSavedJson' rows='5' cols='60' disabled='disabled'></textarea>
Here's a place to start. As #Nathan Fisher mentioned, it would make sense to create a Room and potentially even a Furniture class.
HTML
<button data-bind="click: addRoom"></button>
<div data-bind='foreach: rooms'>
<button data-bind="click: $parent.removeRoom"></button>
<button data-bind="click: $parent.addFurnitureToRoom"></button>
<div data-bind="foreach: furnitures">
<button data-bind="click: $parents[1].removeFurnitureFromRoom.bind($root, $data, $parent)"></button>
</div>
</div>
JS
function HouseViewModel (rooms) {
var self = this;
this.rooms = ko.observableArray(rooms || []);
this.addRoom = function () {
self.rooms.push({
name: ko.observable(''),
furnitures: ko.observableArray(),
});
};
this.removeRoom = function (room) {
self.rooms.remove(room);
};
this.addFurnitureToRoom = function (room) {
room.furnitures.push({
name: ko.observable(''),
});
};
self.removeFurnitureFromRoom = function (furniture, room) {
room.furnitures.remove(furniture);
};
};

jQuery and KnockOutJS linking a slider to the Cart example

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;
});

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