I have a KO model with a lot of nested objects and collections of nested objects. I use the ko.mappings mapping options to make sure they get properly generated during model creation, but the value gets cleared. Trying to start with a simple drop down menu and bind objects to it:
<select id="myList" name="SelectedSurvey" id="SelectedSurvey" class="form-control"
data-bind="options: AvailableSurveys, optionsText: 'Name', value: SelectedSurvey,
optionsCaption: '-- Select Survey --'"></select>
Then, take some data, when creating a new object on the page, it works fine, but when trying to edit an existing record from the same page, the drop down value is never selected. When I check viewModel.SelectedSurvey() the value is undefined, but it only becomes undefined after I callapplyBindings()`. Why is this happening? How can I fix it?
Here is a working example: http://jsfiddle.net/6wLcr52y/3/
If you open the console and run it, you'll see the log before applyBindings() is called the full ViewModel, and the nested object SelectedSurvey() are properly komapping objects that have values, but then after it's called. SelectedSurvey() becomes undefined and as such my dropdown list never has a selected value.
The SelectedValue parameter doesn't seem to want to accept an object as a valid value. It therefore clears the SelectedValue observable as soon as you apply bindings thinking that no valid value was provided.
As a workaround, I created a solution that uses the optionsValue parameter to accept the 'Id' property as a value. This will allow you to maintain control over which survey is selected by passing in merely an 'Id' value as opposed to an entire Survey object.
This requires that you update your Knockout version to 3.4.0. Are you locked in to using 2.2.1 for any reason?
I also updated your jsfiddle to include more readable implementation of the mapping plugin. Hopefully this will fix your issue:
var ViewModel = function(data) {
var self = this;
self.AvailableSurveys = ko.mapping.fromJS(data.AvailableSurveys);
self.SelectedId = ko.observable(data.SelectedSurvey.Id);
self.SelectedSurvey = ko.pureComputed(function () {
for (i = 0; i < self.AvailableSurveys().length; i++) {
if (self.AvailableSurveys()[i].Id() === self.SelectedId()) {
return self.AvailableSurveys()[i];
}
}
})
}
Fiddle: http://jsfiddle.net/dw1284/v1L5tw6h/3/
Related
I am having nested objects with multiple properties. I have created an input field on the UI for every property and value is changed by using [(ngModel)]. I want to implement a functionality where if any value in the object is changed I should be able to detect it and enable the reset option to show initial values again.
I have tried exploring this and the majority of answers were related to form controls only.
if you bind with two-way binding to data you will not be able to hold old record. You can write a fn to compare to objects if you bind with [ngModel].Here is pseudo.
in .ts
value = {}
onValueChange(data:any){
const changedData = getChangedData(this.value, data);
}
in .html
<input [ngModel]="value" (ngModelChange)="onValueChange($event)">
}
I hava a selectOptions ajax-based asincronous loader; it accepts remote address and returns an observable array, correctly populated with descriptions and keyvalues to be accepted by the following binding
<select data-bind="value: selectedVal, options: opts, optionsText: 'desc', optionsValue:'key', optionsCaption: ''"/></div>
The fact is that, being asincronous, when I trigger a select options change, based on some user actions, I assign it to my model observable array, I do not get the select popuated, but remains empty.
mymodel.opts = loadOptions("<remoteaddress>");
I know when the second line is called the anwer is not arrived yet, but the returned value is an observableArray, so it should respond correctly whenever is populated, having been assigned to an observable array binded with the ui.
If I hardcode the returned object from the ajax call (when it returns) taking it from console.log in Firefox, or if I pass the observable array opts into the loadOptions, and change it to build up the opts inside it, then it works, but I really need to use loadOptions as is, asincronous. I also tried to append mymodel.opts.valueHasMutated(), but yet ko cannot use the newlly arrived observableArray.
If possible leaving intact the options loader, and if possible without using a custom binding, can I use the incoming observable array for binding when it will be ready?
The problem you've got is that when this line runs:
mymodel.opts = loadOptions("<remoteaddress>");
it's replacing the entire observable array with a different observableArray, rather than updating the current one. You need to update the existing one - can you change loadOptions to return a normal array, rather than an observable one? You can then do:
//clear any existing entries
mymodel.opts.removeAll();
//push the new entries in
mymodel.opts.push.apply(mymodel.opts, loadOptions("<remoteaddress>"));
I am working using breeze + knockout using as base hottowel template.
Using this template I am able to make some binding like:
<select data-bind="options: $parent.rolesList, value: role">
Where rolelist it's a ko.observableArray() and role it's a string property. everything it's fine with this example.
My issue begin when I try make some more complex databinding, i.e:
select data-bind="options: mycollection, optionsText: 'dictionary().name()'"></select>
In this new example mycollection it's ko.observableArray() and dictionary().name() it's "object property" plus a string property.
If I go to chrome debuger I can see that mycollection()[0].dictionary().name() has a value.
So I dont' know what I am doing wrong.
Here you are a short example( you can see that I am using just dictionary.name) http://jsfiddle.net/rolandomartinezg/BN2ZP/2/
When passing a string to optionsText, Knockout simply tries to key into your object. It does not handle nested keys or run the string as JavaScript.
However, you can instead choose to pass a function for optionsText which takes in the item as the first argument and you can return whatever value that you like.
For example, in your jsFiddle, you could define a function like:
self.getOptionText = function(item) {
return item.dictionary.name;
};
Then, bind against it like:
<select data-bind="options: myCollection, optionsText: getOptionText"></select>
In your sample from the question, then you would just do item.dictionary().name() if you are dealing with observables.
I have defined my select as below
<select id="selProductType" data-bind="options: productType , value: editProductType , optionsText: 'Name'" />
Below is my code to fill the select in my view model
// Loading combobox with Product Types
$.getJSON("../RestService/Product/AllProductTypes",
function (allData) {
var mappedProductType = $.map(allData, function (item) {
console.log(item.Id + ' ' + item.Name);
return new productType(item);
});
self.productType(mappedProductType);
});
When i initialise page i want to set default value to select. So i tried below
self.editProductType(4);
But it gives me TypeErrror saying object 4 has no method Id.
How to go about it. I have also gone through similar post in stackoverflow but no luck.
Knockout JS binding initial/default value of dropdown (select) list
If you don't specify the :
optionsValue: 'Id'
in data-bind, the binding value in this case must be an object in productType array.
You can select the default object:
self.editProductType(self.productType()[2]);
What is optionsValue?
Similar to optionsText, you can also pass an additional parameter called optionsValue to specify which of the objects’ properties should be used to set the value attribute on the elements that KO generates. You can also specify a JavaScript function to determine this value. This function will receive the selected item as its only argument and should return a string to use for the element’s value attribute.
Typically you’d only want to use optionsValue as a way of ensuring that KO can correctly retain selection when you update the set of available options. For example, if you’re repeatedly getting a list of “car” objects via Ajax calls and want to ensure that the selected car is preserved, you might need to set optionsValue to "carId" or whatever unique identifier each “car” object has, otherwise KO won’t necessarily know which of the previous “car” objects corresponds to which of the new ones.
read more at: http://knockoutjs.com/documentation/options-binding.html
First: I am new to knockout js, and trying to wrap my head around the knockout/mvvm way of thinking, so please forgive me if my question turns out to be trivial.
What I have is a the following case: I have a knockout viewmodel containing an observableArray of selected ticket objects. This array represents a user-defined selection/subset of a bigger set of tickets. The whole set of tickets are listed in a jqgrid table, and each row has a checkbox which is supposed to be telling whether each ticket is selected or not. This means that the value of the checkbox needs to be updated whenever the "selectedTickets" array changes. In addition to this I also want the user to be able to click each checkbox in order to add/remove a ticket from the selection. Would seem like a fairly acceptable piece of functionality, right?
I do however have trouble seeing how I could use the knockout "checked" binding in order to achieve this. My first idea was to use a computed/dependent observable on the viewmodel object called "isSelected" which would reflect changes in the selectedTickets array and return true or false based on whether a ticket is in the selectedTickets array or not. The first problem here is that I then need to pass a parameter to the computed observable saying which ticket ID it's supposed to look up, and from what I can see that only works on a writable computed observable. Getting the state for the checkbox does however not seem like a write operation, so something already started to smell. Next issue is that the binding needed to be twoway, as I wanted the user to be able to change the state of each checkbox and having the selectedTickets array being updated accordingly. This is a different operation, as it would actually remove/add tickets to the selectedTickets array. Which would again trigger the computed observable trying to set the state of the checkbox. Seems like these two use cases may end up like an infinite loop if I try to do it this way. I haven't found a good way of combining these two use cases by just using the checked binding for the checkboxes.
I could of course do the event handling on the checkboxes manually, by hooking up listeners to the changed event on the checkboxes and to the selectedTickets array in the knockout viewmodel, but I was hoping this was possible to more automated with knockout bindings.
Hope there are some knockout masters out there who can guide me onto a good path, as I feel I've steered off-track with this one.
When using knockout.js, you need to stop doing things by halves - if you have a list of items, the data belongs in the viewmodel (not only the select items), and only the appearance is defined by the view.
Thus, I'd recommend an observable array items of type Item, which has a property isSelected - the selected items can then be made accessible via a computed observable:
var Item = function(name) {
this.name = ko.observable(name);
this.isSelected = ko.observable(false);
};
var ViewModel = function() {
var self = this;
self.items = ko.observableArray([
new Item('Foo'), new Item('Bar'), new Item('Foo Bar')
]);
self.selectedItems = ko.computed(function() {
return ko.utils.arrayFilter(self.items(), function(item) {
return item.isSelected();
});
});
};
http://jsfiddle.net/htZfX/