Knockout nested foreach add and remove not working - javascript

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

Related

Knockout JS : click-to-edit in 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" />

Accessing part of the data in Knockoutjs with multiple models

I just want to understand why does Knockout.js does not let me access part of the model data. Is it because I am binding the model to the div containing all the submodels (Form in this example) or I am thinking it wrong?
For example, in this jsfiddle http://jsfiddle.net/zv6hauft/1/
I want to save just fare information but leave out bus information. So even if i just pass "fare_lines" in the example, it shows me all the models in the console.
<!doctype html>
<script src="/javascript/knockout-3.3.0.js"></script>
<form id="transport_form" method="post">
<table class="table table-condensed required" data-bind='visible: bus_lines().length > 0'>
<thead>
<tr>
<th>Bus</th>
</tr>
</thead>
<tbody data-bind='foreach: bus_lines'>
<tr>
<td>
<input name="bus_date" type="text" class="bus_date" value=" " data-bind='value: bus_desc' required/>
</td>
<td><a href='#' id="bus_remove" data-bind='click: $parent.removeBusLine'>Delete</a>
</td>
</tr>
</tbody>
</table>
<div class="controls">
<button id="bus_button" data-bind='click: addBusLine'>Add Bus</button>
</div>
<div id="fare_info_table">
<table data-bind='visible: fare_lines().length > 0'>
<thead>
<tr>
<th>Fare</th>
</tr>
</thead>
<tbody class="table required" data-bind='foreach: fare_lines'>
<tr>
<td>
<input id="fare_amnt" data-bind='value: fare_desc' required />
</td>
<td><a href='#' id="fare_remove" data-bind='click:$parent.remove_fare_line'>Delete</a>
</td>
</tr>
</tbody>
</table>
<div class="controls">
<button id="fare_button" data-bind='click: add_fare_line'>Add fare</button>
</div>
</div>
</br>
</br>
<div class="control-group">
<div class="controls">
<button type='submit' data-bind="click: save_record">Submit</button>
</div>
</div>
</form>
</html>
<script>
//Bus Model
var Bus_model = function () {
var self = this;
self.bus_desc = ko.observable();
};
var fare_model = function () {
var self = this;
self.fare_desc = ko.observable();
}
var operations_bus_fare = function () {
var self = this;
//Study Schedule Operations
self.bus_lines = ko.observableArray([new Bus_model()]);
self.addBusLine = function () {
self.bus_lines.push(new Bus_model())
};
self.removeBusLine = function (Bus_line) {
self.bus_lines.remove(Bus_line)
};
//Fare operations
self.fare_lines = ko.observableArray([new fare_model()]);
self.add_fare_line = function () {
self.fare_lines.push(new fare_model())
};
self.remove_fare_line = function (fare_line) {
self.fare_lines.remove(fare_line)
};
self.save_record = function (fare_lines) {
var saveData2 = ko.toJSON(fare_lines);
console.log(saveData2);
alert(saveData2);
};
};
ko.applyBindings(new operations_bus_fare(), document.getElementById("transport_form"));
</script>
You can access part of ViewModel my doing like this
ViewModel:
self.save_record = function (data) { // we get entire vm here as param
var saveData2 = ko.toJSON(data.fare_lines); // access required part
console.log(saveData2);
alert(saveData2);
};
Working fiddle here

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>

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

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

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