Initial selection on dropdowns with objects using

2019-05-23 11:15发布

问题:

I have a model that looks something like this (not actual code so don't mind the possible mistyping).

model = function(option, items) {
    self = this;
    self.options = options;
    self.items = ko.mapping.fromJS(items);
}

The options coantains list of objects that one can select from in a dropdown. The items also contains a list of object where each object has a identical object like one in the options-list.

I then foreach over the items list and display an dropdown box on each row. I here need the object in the current items from the items list to be the selected option. When I however don't set an optionValue but only try to match on whole object it doesn't work ... My observable then however works fine and all the subscribing fields for the whole object get updated with the new selection. I however got the initial selection to work with optionValue and Id like below.

<select data-bind="options: $parent.options, optionsValue:'Id', optionsText: 'Name', value: item.Id"></select> 

My problem now is that only the element bound to the Id gets updated? I need to have all the properties of the current item updated even if it's only the Id that changes now when I change something in the dropdown.

How should I do this?

回答1:

So my take on this is the following.

  • You have a set of 'options'. Each option has some properties and an Id
  • You have a set of 'items' where each item has one property containing an object which equals one of the objects inside options. So each 'item' has a selected 'option'.

Unlike c# and other high-level environments javascript does not have a built-in concept of equality. When you do something like objA == objB it will check for reference equality (not true for primitive types such as numbers and strings), ie that the two variables actually reference the same object. In .NET for example a class could implement IEquatable<T> (and a operator overload) so that objA == objB results in some custom comparison which will determine whether or not the two different objects could be considered equal.

So when working with knockout and drop down and such it's important to remember that in order for knockout to match you'll have to ensure that the compared objects are indeed the same.

In your case I've tweaked your model a bit. I've assumed that the selection option property of items is called SelectedOption.

function model(options, items) {
    self = this;
    self.options = options;  
    self.items = ko.mapping.fromJS(items);

    // Loop over each of the items and swap out the server-side provided option object
    // with the corresponding option from the options parameter.
    ko.utils.arrayForEach(self.items(), function(item) {
        item.SelectedOption = ko.observable(
            ko.utils.arrayFirst(self.options, function(option) { return option.Id == item.SelectedOption.Id(); })
        );
    });
}

Since you're using ko.mapping I'm assuming that the options and items parameters are provided as plain javascript objects somehow (Ajax, inline js).

opts = [ 
    { Id: 1, Name: "Option 1" },
    { Id: 2, Name: "Option 2" },
    { Id: 3, Name: "Option 3" }
];

var items = [
    { Id: 1, Name: "Item 1", SelectedOption: { Id: 1, Name: "Option 1" } },
    { Id: 2, Name: "Item 2", SelectedOption: { Id: 2, Name: "Option 2" } },
    { Id: 3, Name: "Item 3", SelectedOption: { Id: 3, Name: "Option 3" } }
];

var viewModel = new model(opts, items);

Since the options contained in the SelectedOption parameter of each item is the exact same as the ones in the options property knockout is now able to compare them for equality and you can use it in your bindings as such:

<div data-bind="foreach: items">
    <select data-bind="options: $parent.options, optionsText: 'Name', value: SelectedOption"></select>
</div>

Test it out at jsfiddle: http://jsfiddle.net/niik/HDsKC/