Reputation: 717
I have a json array that has a device name and quantity. If the quantity is two and the user adds two items to the foreach for that name (they select from the drop down field) I'd like to hide that device name option from the drop down menu.
*Edit* Rune seems that he has the correct answer, however I cannot figure out how to integrate it into my code. Here is my code: http://jsfiddle.net/QTUqD/1/
<form id="extMngForm">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Extension</th>
<th>Name</th>
<th>Email</th>
<th>Voicemail Pin</th>
<th>Device</th>
<th>MAC Address</th>
<th>Ship To</th>
<th style="width: 100px; text-align:right;" />
</tr>
</thead>
<tbody data-bind=" template:{name:templateToUse, foreach: pagedList }"></tbody>
</table>
<!-- ko if: pagedList().length < extQty() -->
<p class="pull-right"><a class="btn btn-primary" data-bind="click: $root.add" href="#" title="edit"><i class="icon-plus"></i> Add Extension</a></p>
<!-- /ko -->
<div class="pagination pull-left">
<ul><li data-bind="css: { disabled: pageIndex() === 0 }"><a href="#" data-bind="click: previousPage">Previous</a></li></ul>
<ul data-bind="foreach: allPages">
<li data-bind="css: { active: $data.pageNumber === ($root.pageIndex() + 1) }"><a href="#" data-bind="text: $data.pageNumber, click: function() { $root.moveToPage($data.pageNumber-1); }"></a></li>
</ul>
<ul><li data-bind="css: { disabled: pageIndex() === maxPageIndex() }"><a href="#" data-bind="click: nextPage">Next</a></li></ul>
</div>
<br clear="all" />
<script id="extItems" type="text/html">
<tr>
<td style="width:20px;" data-bind="text: extension"></td>
<td data-bind="text: name"></td>
<td data-bind="text: email"></td>
<td style="width:20px;" data-bind="text: vmpin"></td>
<td data-bind="text: device"></td>
<td data-bind="text: macAddress"></td>
<td data-bind="text: shipTo"></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="editExts" type="text/html">
<tr>
<td style="width:20px;"><input data-errorposition="b" class="required" name="extension" data-bind="value: extension" /></td>
<td><input data-errorposition="b" class="required" name="name" data-bind="value: name" /></td>
<td><input data-errorposition="b" class="required" name="email" data-bind="value: email" /></td>
<td style="width:20px;"><input data-errorposition="b" class="required" name="vmpin" data-bind="value: vmpin" /></td>
<td>
<select style="width:100px;" data-bind="options: $root.devices, optionsText: 'name', optionsValue: 'id', value: device"></select>
</td>
<td><input data-errorposition="b" name="macAddress" data-bind="value: macAddress" /></td>
<td><select style="width:100px;" data-bind="options: $root.addressList, optionsText: 'locationName', optionsValue: 'shipping_address_street', value: shipTo"></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>
<form id="extMngForm">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Extension</th>
<th>Name</th>
<th>Email</th>
<th>Voicemail Pin</th>
<th>Device</th>
<th>MAC Address</th>
<th>Ship To</th>
<th style="width: 100px; text-align:right;" />
</tr>
</thead>
<tbody data-bind=" template:{name:templateToUse, foreach: pagedList }"></tbody>
</table>
<!-- ko if: pagedList().length < extQty() -->
<p class="pull-right"><a class="btn btn-primary" data-bind="click: $root.add" href="#" title="edit"><i class="icon-plus"></i> Add Extension</a></p>
<!-- /ko -->
<div class="pagination pull-left">
<ul><li data-bind="css: { disabled: pageIndex() === 0 }"><a href="#" data-bind="click: previousPage">Previous</a></li></ul>
<ul data-bind="foreach: allPages">
<li data-bind="css: { active: $data.pageNumber === ($root.pageIndex() + 1) }"><a href="#" data-bind="text: $data.pageNumber, click: function() { $root.moveToPage($data.pageNumber-1); }"></a></li>
</ul>
<ul><li data-bind="css: { disabled: pageIndex() === maxPageIndex() }"><a href="#" data-bind="click: nextPage">Next</a></li></ul>
</div>
<br clear="all" />
<script id="extItems" type="text/html">
<tr>
<td style="width:20px;" data-bind="text: extension"></td>
<td data-bind="text: name"></td>
<td data-bind="text: email"></td>
<td style="width:20px;" data-bind="text: vmpin"></td>
<td data-bind="text: device"></td>
<td data-bind="text: macAddress"></td>
<td data-bind="text: shipTo"></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="editExts" type="text/html">
<tr>
<td style="width:20px;"><input data-errorposition="b" class="required" name="extension" data-bind="value: extension" /></td>
<td><input data-errorposition="b" class="required" name="name" data-bind="value: name" /></td>
<td><input data-errorposition="b" class="required" name="email" data-bind="value: email" /></td>
<td style="width:20px;"><input data-errorposition="b" class="required" name="vmpin" data-bind="value: vmpin" /></td>
<td>
<select style="width:100px;" data-bind="options: $root.devices, optionsText: 'name', optionsValue: 'id', value: device"></select>
</td>
<td><input data-errorposition="b" name="macAddress" data-bind="value: macAddress" /></td>
<td><select style="width:100px;" data-bind="options: $root.addressList, optionsText: 'locationName', optionsValue: 'shipping_address_street', value: shipTo"></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>
window.ExtListViewModel = 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.extQty = ko.observable();
self.devices = ko.observableArray(['devices']);
self.addressList = ko.observableArray(['addressList']);
self.edit = function (item) {
if($('#extMngForm').valid()) {
self.selectedItem(item);
}
};
self.cancel = function () {
self.selectedItem(null);
};
self.add = function () {
if($('#extMngForm').valid()) {
var newItem = new Extension();
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 () {
if($('#extMngForm').valid()) {
self.selectedItem(null);
};
};
self.templateToUse = function (item) {
return self.selectedItem() === item ? 'editExts' : 'extItems';
};
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(ExtListViewModel, document.getElementById('extMng'));
function Extension(extension, name, email, vmpin, device, macAddress, shipTo){
this.extension = ko.observable(extension);
this.name = ko.observable(name);
this.email = ko.observable(email);
this.vmpin = ko.observable(vmpin);
this.device = ko.observable(device);
this.macAddress = ko.observable(macAddress);
this.shipTo = ko.observable(shipTo);
}
Upvotes: 0
Views: 2502
Reputation: 2910
Here's an approach based on adding a new computed for available devices on the main viewmodel object, plus a helper function. It's not ideal for all cases and there's probably ways to simplify this, but I've run out of juice for now :P
self.availableDevices = ko.computed(function() {
var usedQuantities = {}; // for each device id, store the used quantity
ko.utils.arrayForEach(self.list(), function(item) {
var device = item.device();
if (device) {
usedQuantities[device.id] = 1 + (usedQuantities[device.id] || 0);
}
});
return ko.utils.arrayFilter(self.devices(), function(device) {
var usedQuantity = usedQuantities[device.id] || 0;
return device.quantity > usedQuantity;
});
});
// need this to add back item's selected device to its device-options,
// and to maintain original order of devices
self.devicesForItem = function(item) {
var availableDevices = self.availableDevices();
return ko.utils.arrayFilter(self.devices(), function(device) {
return device === item.device() || availableDevices.indexOf(device) !== -1;
});
}
and the corresponding binding (note: no use of optionsValue):
<select style="width:100px;" data-bind="options: $root.devicesForItem($data), optionsText: 'name', value: device"></select>
Updated JSFiddle: http://jsfiddle.net/antishok/QTUqD/6/
Some other small changes I made to your fiddle so it'll work: changed extQty to 20, added example devices, fixed element ID for ko.applyBindings, commented form validation stuff.. maybe a couple more ;p (also included different bootstrap css)
Upvotes: 1
Reputation: 3239
Without knowing how your markup or view model looks like, I'm assuming you're mapping your devices. I would map the following "Device" object and work with it. To be more specific, I'd have to see your code in a JSBin or something.
var Device = function(data) {
var self = this;
ko.mapping.fromJS(data, { }, self);
// self.name added via mapping
// self.quantity added via mapping
self.numberOfDevicesShown = ko.observable();
self.showDevice = ko.computed(function(){ // use this property in the markup
return self.numberOfDevicesShown() >= self.quantitity();
})
};
var viewModelMapping = {
'devices': {
create: function(options) {
return new Device(options.data);
}
};
var ViewModel = function(data) {
var self = this;
ko.mapping.fromJS(data,viewModelMapping,self);
}
$(document).ready(function () {
vm = new ViewModel(initialViewModelData);
ko.applyBindings(vm);
});
In your markup you can do something like this:
<select data-bind="foreach: devices">
<!-- ko if: showDevice -->
<option></option>
<!-- /ko -->
</select>
You can read more about mapping in the Customizing object construction using “create” and Customizing object updating using “update” sections here
Upvotes: 3