-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue with select element binding using options, optionsValue, and value #1269
Comments
Is there any solution for that problem ? I came across the exact same issue. |
same issue here: person model has a title object and I am able to set the value of the title, however I am unable to select the selected value for a current Title when in edit form mode. really annoying when your working with databases like ravendb and you would store the full object, rather than a lookup reference. |
I agree that a custom comparison function could be handy. Also, not sure if this might help: Foreign Key Extender. |
hi, |
I think I have found a solution for this, I use the following code to fetch in the object from my observable when I am binding / building my model. The changes should be quick and easy to make and works like a charm item to enable search ko.observableArray.fn.find = function (value) {
if (value !== null) {
return ko.utils.arrayFirst(this(), function(item) {
return item.Id == value.Id;
});
} else {
return false;
}
}; my original model, with broken select values var itemModel = function(item) {
this.Id = ko.observable();
this.DocumentElement = ko.observable();
this.DocumentColumnId = ko.observable();
this.OrderSequence = ko.observable();
this.Required = ko.observable(false);
this.ErrorMessage = ko.observable();
this.FormType = ko.observable();
this.Label = ko.observable();
this.OptionType = ko.observable();
this.InvocableType = ko.observable();
this.PlaceholderText = ko.observable();
var self = this;
this.initialize = function(data) {
self.Id(data.Id);
/// notice the code here, observableitems.Find(exisiting value);
self.DocumentElement(currentDoc().DocumentElements.find(data.DocumentElement));
self.DocumentColumnId(data.DocumentColumnId);
self.OrderSequence(data.OrderSequence);
self.Required(data.Required);
self.ErrorMessage(data.ErrorMessage);
/// notice the code here, currentElement.Find(exisiting value);
self.FormType(formTypes.find(data.FormType));
self.Label(data.Label);
self.OptionType(optionTypes.find(data.OptionType));
/// notice the code here, currentElement.Find(exisiting value);
self.InvocableType(invocableTypes.find(data.InvocableType));
self.PlaceholderText(data.PlaceholderText);
};
self.initialize(item);
this.cancel = function() {
//cancel code (clears objects / observables )
};
this.updateItem = function () {
//code to update / push
};
this.delete = function() {
// delete code
};
this.createItem = function () {
//code to save in db
};
}; invocableTypes = ko.observableArray([{},{},{},{},{}]); // some values / individual objects my binding <div class="row" data-bind="visible: currentItem, with: currentItem" id="additem">
<div class="col-md-12">
<h2>Create / Edit Item</h2>
<div class="form-group">
<label>Order Sequence</label>
<input type="text" class="form-control" data-bind="value:OrderSequence" />
<br />
<label>Choose PDF item</label>
<select data-bind="options: $root.currentDoc().DocumentElements, optionsText:'ElementId', value:DocumentElement, optionsCaption: 'Choose Item'"></select>
<br />
<label>Required Value</label>
<input type="checkbox" data-bind="checked:Required" style="float:left; margin-left: 5px;" />
<br class="clear clearfix" />
<label>Form Input Type</label>
<select data-bind="options: $root.formTypes, optionsText:'Name', value:FormType, optionsCaption: 'Choose Type'"></select>
<br />
<label>Label</label>
<input type="text" class="form-control" data-bind="value:Label" />
<br />
<label>Options Type</label>
<select data-bind="options: $root.optionTypes, optionsText:'Name', value:OptionType, optionsCaption: 'N/A'"></select>
<br />
<label>Dynamic Type</label>
<select data-bind="options: $root.invocableTypes, optionsText:'Name', value:InvocableType, optionsCaption: 'Choose N/A'"></select>
<br />
<label>PlaceHolder (or dropdown first)</label>
<input type="text" class="form-control" data-bind="value:PlaceholderText" />
<br />
<label>Error Message ( for edit )</label>
<input type="text" class="form-control" data-bind="value:ErrorMessage" />
<br />
<br />
<!-- ko if:Id -->
<a class="btn btn-success" data-bind="click:updateItem">Update</a>
<!-- /ko -->
<!-- ko ifnot: Id -->
<a class="btn btn-success" data-bind="click:createItem">Create</a>
<!-- /ko -->
<a class="btn btn-danger" data-bind="click:cancel">Cancel</a>
</div>
</div>
</div> It took me a while to figure this all out. The problem here is that when you have a copy of an object The above example is missing the base model currentDoc, however this is not needed to explain how the code works, this knockout allows for the creation of forms. Hope this helps, works for me Dave |
@dcroe, If you can put together an example that works in 2.x but not in 3.x, that would help us determine whether this is a bug. |
hi,
i'm not familiar with the internals of ko, but for me it looks like the "NEWVALUE" doesn't reach the valueUpdateHandler of the value binding, instead it uses the value of the valueAccessor() which hasn't been updated at that time ? |
Can you provide a link to the jsfiddle? Also you say that it works in 2.1 and 3.2. Is that correct or do you mean 2.2? |
this is the version which works in 2.1 and 3.2, but the selected value is only the name and not an object: |
in 2.1 the dependency tracking does not fire a change event which causes the newvalue to be lost... |
@mbest Hi, i you want to replicate this, use my code and remove the find. I think normally you only have this issue if your using a nosql style of document storage. With SQL / Rational, as you know, you would just reference an object with an int or string, but with nosql you would just store the entire object. for example: var Titles = ko.observableArray([{Id:1, Value:'Mr'},{Id:1, Value:'Ms'},{Id:1, Value:'Miss'},{Id:1, Value:'Mrs'}]);
var personModel = function(){
this.Id = ko.observable();
this.Name = ko.observable();
this.Title = ko.observable(); // in rational, this would just be the value of Id
// but as a document, we will keep the entire object from Titles.
}; html binding and the error <!-- image here that we are With: personModel -->
<select data-bind="
options: $root.Titles,
optionsText:'Value',
value:Title <!-- this being personModel .Title -->, optionsCaption: 'Choose Title'">
</select> Value in this instance is never selected, no matter what person.Title is equal to Hence my code above, that takes the actual title from titles and updates the person. Value is the same, there are no changes here, just KO never recognises a Title unless it is from the Titles Array. |
@magic-uyr: that is what i mean it works with optionsValue: 'Id' , but than you have only the id and not the object have a look at: |
after further investigation it looks like adding this option: valueAllowUnset=true will fix my problem. |
I have the same issue, it's very annoying. |
why it gives me an undefined after bind the value |
@kevinpan - Perhaps you need to add the option, |
@mbest Thank You! |
This binding handler is working for me (so far): ko.bindingHandlers.valueByKey = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var value = allBindings().value;
var valueKey = value()[valueAccessor()];
var match = ko.utils.arrayFirst(allBindings().options(), i => {
var listItemKey = i[valueAccessor()];
return listItemKey == valueKey;
});
allBindings().value(match);
},
}; <select data-bind="options: $parent:countryTypes, value: country, valueByKey: 'id', optionsText:'countryName'></select>
Update: |
I do have the same problem. Something like |
I submitted a pull request #2157 |
Here is a less intrusive option which can be done without changing knockout core: ko.bindingHandlers.objectSelect = {
after: ['options', 'foreach'],
init: function(element, valueAccessor, allBindings) {
var interceptor = ko.computed({
read: function() {
var value = ko.utils.unwrapObservable(valueAccessor());
var optionsValue = ko.utils.unwrapObservable(allBindings.get('optionsValue'));
return value && value[optionsValue];
},
write: function(value) {
var optionsValue = ko.utils.unwrapObservable(allBindings.get('optionsValue'));
var options = ko.utils.unwrapObservable(allBindings.get('options'));
var obj = ko.utils.arrayFirst(options, function(item) {
return item[optionsValue] == value;
});
valueAccessor()(obj);
}
});
ko.applyBindingsToNode(element, {
value: interceptor
});
}
}; Then replace value by objectSelect and continue using optionsValue. |
@asfernandes, that enables us to use objects as values of the |
@ZzZombo yeah. |
This has been around a while, and thinking about this now, I'm pretty sure I want to keep the bindings as they are without adding a "comparer" option. Why?
|
@mbest please consider following example: |
Correct. And I don't think it should be the binding that does this.
If a select box is displaying 1000 values, iterating over those 1000 values is already happening. |
Knockout 3.0.0
Assume the following:
I have a complex object (lets say Person) who has a reference variable (favoritePet) to another complex object (Pet). The value that can be held for the 'favoritePet' references comes from an asynchronous HTTP request. Meaning, if a value is populated in the 'favoritePet' reference, then the object selected is equivalent to another object in the fetched Pet instances, but is not the same object in memory.
According to the documentation, I need to provide the binding for 'optionsValue' and I assumed this would work:
However, I found that it does the following:
I dug through the code and I believe this is because the code upon section, tries to determine the data object that was selected by comparing the option elements value with the list of available data objects and upon setting selection, tries to compare the elements option text with the observable value. So in both instances trying to compare the option elements value (a string) with the object (an object), which will never be equal.
I can resolve the second problem by changing the data-binding to:
Now when a value is assigned to the reference variable, then it can properly resolve which option to select by default because the assigned value for the binding is the observable values 'id' field, which eventually is equivalent to an the 'id' field of one of the instances of available pets.
However, this causes a different problem when there is no value assigned to the 'favoritePet' reference variable as 'favoritePet()' resolves to null, throwing an error.
I believe all this is again due to the code trying to determine the appropriate model to both select and assign by checking the equivalence of the value and the 'optionsValue' of each available data object.
Instead i think the code should be checking for equivalency between the values after begin passed through the 'optionsValue' method. Meaning this binding is valid:
and equivalency is done by calling the 'optionsValue' function on BOTH the assigned value and the data object being compared.
Or Knockout should provide a means of letting the user define an equivalency method
The text was updated successfully, but these errors were encountered: