I am new in knockoutjs, I saw an example, which moves up and down array values by selecting the dropdown values of the index in the option. But its problem is they dont't move the values correctly. And after changing the array position options in select box will be changed..
var viewModel = function() {
var self = this;
var Item = function(name, pos) {
this.name = ko.observable(name);
this.position = ko.observable(pos);
var oldPosition = pos;
this.position.subscribe(function(newValue) {
self.reposition(this, oldPosition, newValue);
oldPosition = newValue;
}, this);
};
this.items = [
new Item("item Three", "3"),
new Item("item One", "1"),
new Item("item Two", "2"),
new Item("item Five", "5"),
new Item("item Four", "4"),
new Item("item Six", "6")
];
self.orderedItems = ko.computed(function() {
return ko.utils.arrayFilter(this.items, function(item) {
return true;
}).sort(function(a, b) {
return a.position() - b.position();
});
});
self.curName = ko.observable();
self.reposition = function(item, oldPosition, newPosition) {
console.debug("Reposition", item, oldPosition, newPosition);
};
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class='liveExample'>
<ul data-bind="foreach: orderedItems">
<li>
<div> <span data-bind="text: name"> </span> has Position:
<select id="pos" data-bind=" options: orderedItems,
optionsText: 'position',
optionsValue: 'position',
value: position "></select>
</div>
</li>
</ul>
</div>
this is my sample code, i want to show array index position should be show in the dropdown. I want to select the index value in the dropdown the position of the array values should be change but not the opitions. How is it possible with knockout js.
So this one is a little more tricky than normal since you are basing your index on a property of the Item. It's not wrong, it just adds more complexity.
First you have to create the array of "indexes" since you aren't actually changing the index of the items, they are just computed off of the position property.
this.items() has been changed to an observableArray to propagate/bubble changes of the items to other functions. Now you could include an "Add Item" feature, add it to the the items array and everything would updated correctly.
I removed the subscribe function in the Item constructor, it was causing too many issues firing off when it didn't need to. Instead I attached an event handler to the select box that can manage the items and removed the two way binding of the value by getting the value().
Hope this helps and good luck!
var viewModel = function() {
var self = this;
// UPDATED: Removed the subscribe function
var Item = function(name, pos) {
this.name = ko.observable(name);
this.position = ko.observable(pos);
};
// UPDATED: Changed to observable so you can change items here and it will propogate down to the computed functions
this.items = ko.observable([
new Item("item Three", "3"),
new Item("item One", "1"),
new Item("item Two", "2"),
new Item("item Five", "5"),
new Item("item Four", "4"),
new Item("item Six", "6")
]);
// ADDED: Create array of index options based on length
this.positions = ko.computed(function(){
var numArray = [];
for(i = 0; i < self.items().length; i++) {
numArray.push(i + 1)
}
return numArray;
})
self.orderedItems = ko.computed(function() {
return ko.utils.arrayFilter(self.items(), function(item) {
return true;
}).sort(function(a, b) {
return a.position() - b.position();
});
});
self.curName = ko.observable();
/**
* UPDATED: Get item at selected position, change it to the current
* items position, then update current items position to the selected position;
*/
self.reposition = function(item, event) {
var selectedPosition = event.target.value;
var itemAtPosition = ko.utils.arrayFirst(self.items(), function(i){
return i.position() === selectedPosition;
})
itemAtPosition.position(item.position());
item.position(event.target.value)
};
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div class='liveExample'>
<ul data-bind="foreach: orderedItems">
<li>
<div> <span data-bind="text: name"> </span> has Position:
<select id="pos" data-bind=" options: positions(),
value: position(), event: { change: reposition} "></select>
</div>
</li>
</ul>
</div>
Related
Here is a basic knockout.js fiddle of what I want to achieve: https://jsfiddle.net/sr3wy17t/
It does what I want to do, but not exactly in a way I want.
For completeness I will repeat parts of the above fiddle code here:
In View I've got for-each which iterates over an observableArray of items:
<div data-bind="foreach: $root.availableItems">
<div class="switchBox">
<div class="switchName"><strong data-bind="text: ' ' + name()"></strong></div>
<label class="Switch">
<input type="checkbox" data-bind="checked: state">
</label>
</div>
It iterates over elements I have in my availableItems array:
self.availableItems([
new Item(1, "item1", state1, self.onItemStateChange),
new Item(2, "item2", state2, self.onItemStateChange),
new Item(3, "item3", state3, self.onItemStateChange)
]);
as you can see, I also have a function in which i initialize each of those items with observables:
function Item(id, name, state, onChange) {
var self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
self.state = ko.observable(state);
self.state.subscribe(function(newValue) {
onChange(self, newValue);
});
}
Each of the items in an array has state variables (state1, state2, state3), which are boolean and they control which chekbox is checked and which one is not. They are (for the sake of this example) set at the beggining of ViewModel:
var state1 = true;
var state2 = false;
var state3 = false;
In reality state1, state2 and state3 are mapped from server. What I want to achieve, is after I initialize my items with starting state values, I want them to be subscribed on every change of state1, state2 and state3, so that checkbox is checked or not checked, depending on the recieved value from the server.
Currently the code in the fiddle achieves state change by accessing availableItems array like this:
setInterval(()=>{
var itemNoThatChanged=Math.floor(Math.random()*3);
var newState=Math.random()>0.5;
self.availableItems()[itemNoThatChanged].state(newState)
},1000)
The issue here, is that it's not the change in state1 or state2 or state3 that is causing the change, but rather direct access to array of availableItems....
How can I change this code, so that the change of state1, state2 and state3 causes the above behavior like in fiddle?
I need to do this with as least changes to existing code approach as possible, since it affects a lot of other stuff in the original code.
Is this possible to do, and if yes, can someone please explain how to code this in knockout.js?
Since you prefer a minimal change to your existing code;
declare your state1, state2 and state3 variables as observables.
var state1 = ko.observable(true);
var state2 = ko.observable(false);
var state3 = ko.observable(false);
Adjust your Item to accept and use these as-is instead of setting up an observable itself.
function Item(id, name, state, onChange) {
var self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
self.state = state;
self.state.subscribe(function(newValue) {
onChange(self, newValue);
});
}
The runnable example below, shows that a value change of the state1 (observable) variable (triggered from the timer callback) also affects the checkbox, without any array access.
function Item(id, name, state, onChange) {
var self = this;
self.id = ko.observable(id);
self.name = ko.observable(name);
self.state = state;
self.state.subscribe(function(newValue) {
onChange(self, newValue);
});
}
function ViewModel() {
var self = this;
var state1 = ko.observable(true);
var state2 = ko.observable(false);
var state3 = ko.observable(false);
self.availableItems = ko.observableArray([]);
self.activeItemss = ko.computed(function() {
return self.availableItems().filter(function(item) {
return item.state();
});
});
self.onItemStateChange = function(item, newValue) {
console.log("State change event: " + item.name() + " (" + newValue + ")");
};
self.init = function() {
self.availableItems([
new Item(1, "item1", state1, self.onItemStateChange),
new Item(2, "item2", state2, self.onItemStateChange),
new Item(3, "item3", state3, self.onItemStateChange)
]);
setInterval(()=>{
// Simulate a change of state1
state1(!state1());
}, 1000);
};
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
viewModel.init();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="foreach: $root.availableItems">
<div class="switchBox">
<div class="switchName"><strong data-bind="text: ' ' + name()"></strong></div>
<label class="Switch">
<input type="checkbox" data-bind="checked: state">
</label>
</div>
</div>
I have the following code in html:
<ul data-bind="template: {name:'location', foreach:locations}">
</ul>
<script type="text/html" id="location">
<li>
<a href='#' id="search_results" data-bind='text: title' class='w3-bar-item'></a>
</li>
</script>
and the following code in viewModel:
var locations = [ (location lists)
];
var viewModel = {
title: ko.observable("Attractions in Seattle, Washington"),
query: ko.observable(""),
};
viewModel.locations = ko.dependentObservable(function(){
var search = this.query().toLowerCase();
return ko.utils.arrayFilter(locations, function(location) {
return location.title.toLowerCase().indexOf(search) >= 0;
});
}, viewModel);
ko.applyBindings(viewModel);
as shown below:
demo
and there is the following code in one of my regular javascript functions
$("#search_results").on('click', function() {
var context = ko.contextFor(this);
for (var i = 0; i < placeMarkers.length; i++) {
temp = placeMarkers[i].title + ", Seattle";
if (temp == context.$data.title) {
getPlacesDetails(placeMarkers[i], placeInfoWindow);
}
}
});
I am trying to dynamically show the result based on what context the user clicks, but my function works only for the first item in the list (only Space Needle, in this case). How can I fix it? what would be knockout.js-ic way?
+
I wrote like this inside of viewModel:
show_infowindow: function() {
var context = ko.contextFor(this);
for (var i = 0; i < placeMarkers.length; i++) {
temp = placeMarkers[i].title + ", Seattle";
if (temp == context.$data.title) {
getPlacesDetails(placeMarkers[i], placeInfoWindow);
}
}
}
where
<a href='#' data-bind='text: title, click: show_infowindow' class='search_results w3-bar-item'></a>
and now nothing is working, how can I fix this?
I suggest you create viewModel function and use the new operator whenever you have a click function or a computed property (or dependentObservable prior to ko 2.0). This will reduce the pain of debugging and understanding what this means in callbacks.
So remove the jquery click event handler and change your viewmodel to:
var viewModel = function() {
var self = this;
self.title = ko.observable("Attractions in Seattle, Washington");
self.query = ko.observable("");
self.locations = ko.computed(function(){
var search = self.query().toLowerCase();
return ko.utils.arrayFilter(locations, function(location) {
return location.title.toLowerCase().indexOf(search) >= 0;
});
}
self.show_infowindow = function(location){
// "location" parameter has the current location object being clicked
// you can use it directly instead of ko.contextFor(this);
}
};
// don't forget the "new" keyword
ko.applyBindings(new viewModel());
Change your template to add a click binding like this:
<script type="text/html" id="location">
<li>
<a href='#' id="search_results" data-bind='text: title, click:$parent.show_infowindow' class='w3-bar-item'></a>
</li>
</script>
Since you are using the click binding inside a foreach, you need to prefix the click function with $parent keyword to get the proper binding context. Without $parent, knockout will look for show_infowindow in each location object instead of your viewModel.
Here's another useful answer on the differences between viewModel as an object literal vs a function
Here follows my model:
<!-- ko foreach: teamMembers -->
<tr>
<!-- ko foreach: days -->
<td>
<!-- ko foreach: shifts -->
<input type="text" data-bind="value: startTime">
<input type="text" data-bind="value: endTime">
<!-- /ko -->
</td>
<!-- /ko -->
</tr>
<!-- /ko -->
and my viewmodel:
function TimeCardViewModel() {
var self = this;
self.teamMembers=ko.observableArray();
}
function TeamMemberViewModel(data){
var self=this;
self.days=ko.observableArray();
for (var i=0; i<7; i++) {
self.days.push(new DayViewModel(...);
}
}
function DayViewModel(shifts){
var self=this;
self.shifts=ko.observableArray();
for (var i=0; i<shifts.length; i++) {
self.shifts.push(new ShiftElementsViewModel(...);
}
}
function ShiftElementsViewModel(a,b,c,d) {
var self=this;
self.startTime=ko.observable(a);
self.endTime=ko.observable(b);
}
var timeCardViewModel=new TimeCardViewModel();
ko.applyBindings(timeCardViewModel);
For each member, we have (for each day of the seven days of the week) a number of shifts. For each shift, we have pairs of startTime-endTime inputs.
As far as the visual result is concerned, there are rows which include all the weekly shifts of a member and it might be the case of multiple shifts per day per member. If we look at the columns, these include all the shifts for all the members for a certain day.
My great problem is that I want, whenever there is a blur event on the DOM element of endTime, to focus on the DOM element of startTime vertically. For example, if we are on Monday and the first member has two shifts I want to focus on the startTime of the second shift of the first member when blur is occurring to the endTime of the first shift and then on the startTime of the first shift on Monday of the second member when blur is occurring to the endTime of the second shift of the first member. The same for Tuesday etc. How may I achieve that? For the time being, the cursor is travelling horizontally.
Here i am showing you one of the approaches by using all knockout. It shows you the logic how to implement yours since I don't have your data sample and you might need to modify it based on your data
Working example : https://jsfiddle.net/kyr6w2x3/48/
HTML:
<table>
<tbody>
<!-- ko foreach: teamMembers -->
<tr>
<td data-bind="text:name"></td>
<!-- ko foreach: days -->
<td>
<h4 data-bind="text:name"></h4>
<input type="text" data-bind="value: startTime ,hasFocus :getFocus">
<input type="text" data-bind="value: endTime ,event:{blur: $root.endTimeBlur}">
</td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
</table>
JS:
var data ={
teamMembers: [{
Name: "Member A",
id:1,
days: [{startTime: '8:00',endTime: "4:00" ,name:'Monday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Tuesday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Wednesday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Thursday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Friday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Saturday',parentId:1},{startTime: '8:00',endTime: "4:00",name:'Sunday',parentId:1}]
},
{
Name: "Member B",
id:2,
days: [{startTime: '8:00',endTime: "4:00" ,name:'Monday' ,parentId:2},{startTime: '8:00',endTime: "4:00",name:'Tuesday' ,parentId:2},{startTime: '8:00',endTime: "4:00",name:'Wednesday',parentId:2},{startTime: '8:00',endTime: "4:00",name:'Thursday',parentId:2},{startTime: '8:00',endTime: "4:00",name:'Friday',parentId:2},{startTime: '8:00',endTime: "4:00",name:'Saturday',parentId:2},{startTime: '8:00',endTime: "4:00",name:'Sunday',parentId:2}]
},
{
Name: "Member C",
id:3,
days: [{startTime: '8:00',endTime: "4:00" ,name:'Monday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Tuesday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Wednesday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Thursday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Friday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Saturday',parentId:3},{startTime: '8:00',endTime: "4:00",name:'Sunday',parentId:3}]
},]
}
var memberViewModel = function(data) {
var self = this ;
self.days = ko.observableArray([]);
self.name = ko.observable(data.Name);
self.id = ko.observable(data.id);
self.days($.map(data.days, function (item) {
return new daysViewModel(item);
}));
}
var daysViewModel = function (data){
var self = this ;
self.getFocus = ko.observable(false);
self.startTime = ko.observable(data.startTime);
self.endTime = ko.observable(data.endTime);
self.name = ko.observable(data.name)
self.parentId = ko.observable(data.parentId);
}
function ViewModel() {
var self = this;
self.teamMembers = ko.observableArray([]);
self.teamMembers($.map(data.teamMembers, function (item) {
return new memberViewModel(item);
}));
self.endTimeBlur = function(data){
ko.utils.arrayFirst(self.teamMembers(), function (item,i) {
if (item.id() == data.parentId() && self.teamMembers()[i+1] ) {
//here you set getFocus to true to make next member 's monday gets focus
self.teamMembers()[i+1].days()[0].getFocus(true);
return;
}
});
}
}
ko.applyBindings(new ViewModel());
This should work for you...
jQuery(function($) {
$('body').on("blur", "input[data-bind*='value: endTime']", function() {
var
$t = $(this), // Current input
$td = $t.closest('td'), // Current input's parent td
i = $td.find('input[data-bind*="value: endTime"]').index($t), // Index of current input = current shift index
$target = $td.find('input[data-bind*="value: startTime"]').eq(i + 1); // Target is current shift + 1
if ($target.length) {
$target.focus();
}
});
});
The idea is to bind an event handler to blur event of every input that contains value: endTime in data-bind attribute.
If this handler, we find out the index of endTime input in day, add 1 to it and focus the startTime input with that index in the same td (day) 😉
Since the elements dont exist right after document is loaded (but rendered by knockout), we bind the handler to body for inputs selected by input[data-bind*='value: endTime'].
My advice would be to use a computed tabindex attribute.
In your $root viewmodel, we'll compute an array of shift.start and end observables ordered like you want them to be in terms of focus.
We'll also make a factory method that returns a computed index for each <input> bind.
Each input gets an attr: { 'tabindex': getTabIndex() } binding that makes sure the index stays up to date
This approach will work for people that use the tab key to navigate through the form. Most importantly; now that you have a computed list of sorted input observables, you can easily bind to events to select a previous/next one.
Here's an example:
var Root = function() {
this.members = ko.observableArray([]);
for (var i = 0; i < 5; i += 1) {
this.members.push(new Member());
}
// Note: you could do this in less lines of code, but I wanted
// to be extra transparent to show the structure of the data
// transform.
var orderedShiftInputs = ko.pureComputed(function() {
// We want the days to be leading, so we create a
// two-dimensional array: [[meber1day1, member2day1], [member1day2], etc]
// Note: members _cannot_ skip days
var mergedDays = [];
this.members().forEach(function(member) {
member.days().forEach(function(day, index) {
if (!mergedDays[index]) {
mergedDays[index] = [];
}
mergedDays[index] = mergedDays[index].concat(day);
});
});
// We flatten the 2d-array of days to a list of shifts:
// [member1day1shift1, member1day1shift2, member2day1shift1, etc]
var mergedShifts = mergedDays.reduce(function(shifts, days) {
var allShifts = days.reduce(function(all, day) {
return all.concat(day.shifts());
}, []);
return shifts.concat(allShifts);
}, []);
// We flatten the shifts into an array of start and end observables:
// [member1day1shift1start, member1day1shift1end, etc.]
return mergedShifts.reduce(function(inputs, shift) {
return inputs.concat([shift.start, shift.end]);
}, []);
}, this);
this.getTabIndex = function(data) {
return ko.computed(function() {
// Find the start or end data in our sorted array.
// In this example, we can start with index 1. In your app,
// there might be other input elements before the shifts...
var START_TAB_INDEX = 1;
return orderedShiftInputs().indexOf(data) + START_TAB_INDEX;
}, this);
}.bind(this);
}
var Member = function() {
this.days = ko.observableArray([]);
for (var i = 0; i < 2; i += 1) {
this.days.push(new Day());
}
}
var Day = function() {
this.shifts = ko.observableArray([]);
for (var i = 0; i < 3; i += 1) {
this.shifts.push(new Shift());
}
}
var Shift = function() {
this.start = ko.observable(1);
this.end = ko.observable(2);
}
ko.applyBindings(new Root());
td {
border: 1px solid black
}
input {
display: inline;
width: 30px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<table>
<tbody>
<!-- ko foreach: members -->
<tr>
<td data-bind="text: 'Member-' + $index()"></td>
<!-- ko foreach: days -->
<td>
<span data-bind="text: 'Day ' + $index()"></span>
<br/>
<!-- ko foreach: shifts -->
<input type="text" data-bind="value: start,
attr: { 'tabindex': $root.getTabIndex(start) }">
<input type="text" data-bind="value: end,
attr: { 'tabindex': $root.getTabIndex(end) }">
<!-- /ko -->
</td>
<!-- /ko -->
</tr>
<!-- /ko -->
</tbody>
</table>
I'm new at knockout and I've been searching for an answer to the following question without finding an answer:
I several lists of radiobuttons and I want to store the selected value of the selected radiobutton in each of the radio button lists in an array (first index in the array should contain the selected value from the first radio button list and so on). Can this be done in any way?
See my code below to see what I'm trying to do. Basically I want to store the selected radio button values in the observableArray selectedProducts.
function Product(id, name, room) {
this.id = id;
this.name = name;
this.room = room;
}
var listOfProds = [
new Product(1, 'Prod1', 1),
new Product(2, 'Prod2', 1),
new Product(3, 'Prod3', 1),
new Product(1, 'Prod1', 2),
new Product(2, 'Prod2', 2),
new Product(3, 'Prod3', 2)
];
var viewModel = {
products: ko.observableArray(listOfProds),
selectedProducts: ko.observableArray()
};
ko.applyBindings(viewModel);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<ul data-bind="foreach: products">
<li>
<input type="radio" data-bind="attr: {name: 'room'+room}, checkedValue: $data, checked: $root.selectedProducts" />[
<span data-bind="text: room"></span>]
<span data-bind="text: name"></span>
</li>
</ul>
<hr/>
<div data-bind="text: ko.toJSON($root)"></div>
You can try to bind onclick event and make selectedProducts manually.
Html:
<input type="radio" data-bind="attr: {name: 'room'+room}, click: $root.addSelectedElement" />
Click binding:
self.addSelectedElement = function(data) {
var productInRoomArr = self.selectedProducts().filter(function(product) {
return product.room == data.room;
});
if (productInRoomArr.length > 0) {
var productInRoom = productInRoomArr[0];
if (productInRoom.id == data.id)
return true;
var index = self.selectedProducts.indexOf(productInRoom);
if (index > -1) {
self.selectedProducts.splice(index, 1);
}
}
self.selectedProducts.push(data);
return true;}
JsFiddle
I think your selectedProducts needs to be an array of observables, rather than an observable array, so that the actual contents are monitored, rather than it's length:
this.selectedProducts=[ko.observable(),ko.observable(),ko.observable() ];
Then you can bind the radioboxes as follows:
<input type="radio" data-bind="
attr: {name: 'room'+room},
checkedValue: $data,
checked: $root.selectedProducts[$data.room-1]"/>
There are probably more flexible ways to map the rooms to the length of selectedProducts (and you may want an observableArray of observables if the number of rooms is not fixed), and the $data.room-1 snippet mightn't generalise if your rooms aren't consecutive integers, but this should give you some direction.
The full code is at http://jsfiddle.net/yq6jx8v7/
I have a little knockout form which I am able to duplicate using a button to allow multiple sets of data to be provided.
However, each value for the 'sample rate' select can only be used once. For example, the first form by default is set to 192000. So when I click 'add srate element' the form that is generated should not include 192000 as an option in the Sample rate drop-down.
Obviously if the Sample rate for the first form is set to something else, that value should be removed for the second form, and so on.
How do I remove the already-selected Sample rate from the array when a new form is added, so that it is not available in further copies of the form? It is even possible, given the structure of the view model?
There's a fiddle here: http://jsfiddle.net/3lliot/x3cg131g/
There's a bit of logic included already to prevent more than 6 forms.
Any tips will be appreciated ...
Html code:
<body>
<ul data-bind="foreach: forms">
<li>
<!-- This is a *view* - HTML markup that defines the appearance of your UI -->
<p><span style="color:#AB0002">Sample rate element <span data-bind="text: formNum"></span></span>
</p>
<p>Sample rate (Hz):
<select data-bind="options: $parent.sampleRate, value: selectedSampleRate"></select>
</p>
<p>TDM channels per line:
<select data-bind="options: tdmChans, value: selectedTdmchan"></select>
</p>
</li>
</ul>
<button data-bind="click: addForm">Add <srate> element</button>
<button data-bind="click: removeForm">Remove</button>
<hr/>
<ul data-bind="foreach: forms">
<li>
<!-- render the json -->
<p class="code"><srate id="<span data-bind="text: formNum"></span>">
<br/> <sample_rate><span data-bind="text: selectedSampleRate"></span></sample_rate>
<br/> <tdm_chan><span data-bind="text: selectedTdmchan"></span></tdm_chan>
<br/>
</p>
</li>
</ul>
</body>
JS code:
window.onload = startKnockout;
var formNum;
var i = -1;
function Form() {
var self = this;
// Declare observables
self.selectedSampleRate = ko.observable();
self.selectedTdmchan = ko.observable();
self.formNum = ko.observable();
// Define controls
self.tdmChans = ko.computed(function() {
if (self.selectedSampleRate() == 44100 || self.selectedSampleRate() == 48000) {
return ['2', '4', '8', '16'];
} else if (self.selectedSampleRate() == 88200 || self.selectedSampleRate() == 96000) {
return ['2', '4', '8'];
} else if (self.selectedSampleRate() == 176400 || self.selectedSampleRate() == 192000) {
return ['2', '4'];
} else {
// do nothing
}
}, self);
i++;
self.formNum = i;
}
var Vm = function() {
var self = this;
var item = 0;
self.forms = ko.observableArray([]);
self.forms.push(new Form());
item++;
self.addForm = function() {
if (i < 5) {
self.forms.push(new Form());
item++;
} else {
alert("Can't have more than 6 <srate> elements!")
}
};
self.removeForm = function() {
if (item > 1) {
self.forms.splice(item - 1, 1);
item--;
i--;
} else {
alert("Must have at least one <srate> element!")
}
};
self.sampleRate = ko.observableArray(['192000', '176400', '96000', '88200', '48000', '44100']);
return self;
}
// Activates knockout.js
function startKnockout() {
ko.applyBindings(new Vm());
};
Take a look at this:
http://jsfiddle.net/martinbooth/x3cg131g/1/
importantly, compute the available samples rates based on what has been selected in other forms:
self.sampleRates = ko.computed(function(){
var formsValue = forms(),
availableSampleRates = ko.utils.arrayFilter(allSampleRates, function(sampleRate){
return !ko.utils.arrayFirst(formsValue, function(form){
return form != self && form.selectedSampleRate() === sampleRate;
});
});
return availableSampleRates;
});