问题
So I have two dropdown lists that I'm using with knockout and select. When the availablePeople list returns all false (doesn't work properly), I am able to select and retain the person I chose. However, when the available people list works properly, I am not able to see the dropdown selection that I chose. To elaborate on the availablePeople List, if you select a person, that person can no longer be selected in future rows.
HTML
<div>
<table id="tblPossessionChanges">
<thead>
<tr>
<th><a href="#" class="buttonSmall" data-bind="click: addPossessionChange">Add</a></th>
<th>From</th>
<th>To</th>
</tr>
</thead>
<tbody data-bind="foreach: PossessionChanges">
<tr>
<td class="prompt">
<a href="#" class="buttonSmall" data-bind="click: $root.removePossessionChange">Delete</a> </td>
<td>
<select class="form-control"
data-bind="options: $root.AvailableFrom,
value: SelectedFrom,
optionsText: function(i) {return i.Name},
optionsValue: function(i) {return i.ID},
optionsCaption: 'Please select a Person...',
select2: { placeholder: 'Please select a Person...', allowClear: false}"> </select>
</td>
<td>
<select class="form-control"
data-bind="options: $root.AvailableTo,
value: SelectedTo,
optionsText: function(i) {return i.Name},
optionsValue: function(i) {return i.ID},
optionsCaption: 'Please select a Person...',
select2: {placeholder: 'Please select a Person...', allowClear: false}"> </select>
</td>
<td>
<span id="changeTypeSpan" data-bind="text: ChangeType"></span>
</td>
</tr>
</tbody>
</table>
</div>
JS
ko.bindingHandlers.select2 = {
init: function (element, valueAccessor, allBindingsAccessor, bindingContext) {
ko.utils.domNodeDisposal.addDisposeCallback(element,
function() {
$(element).select2('destroy');
});
var select2 = ko.utils.unwrapObservable(allBindingsAccessor().select2);
$(element).select2(select2);
},
update: function (element, valueAccessor, allBindingsAccessor, bindingContext) {
var allBindings = allBindingsAccessor();
if ("value" in allBindings) {
if ((allBindings.select2.multiple || element.multiple) && allBindings.value().constructor != Array) {
$(element).val(allBindings.value().split(',')).trigger('change');
} else {
$(element).val(allBindings.value()).trigger('change');
}
}
$(element).trigger("change");
}
};
function BookPossessionTransferVM() {
var self = this;
self.AllFromList = ([{"IsAdult":false,"Name":"Bob","ID":38438}, {"IsAdult":false,"Name":"Gordon","ID":54686}, {"IsAdult":true,"Name":"Bill","ID":45645}, {"IsAdult":false,"Name":"Sue","ID":1231}, {"IsAdult":false,"Name":"Ling","ID":123578}, {"IsAdult":false,"Name":"Ivy","ID":78945}]);
self.AllToList = ([{"IsAdult":false,"Name":"Adam","ID":38438}, {"IsAdult":false,"Name":"Geoff","ID":54686}, {"IsAdult":true,"Name":"Josh","ID":45645}, {"IsAdult":false,"Name":"Sam","ID":1231}, {"IsAdult":false,"Name":"Ming","ID":123578}, {"IsAdult":false,"Name":"Austin","ID":78945}, {"IsAdult":false,"Name":"Tsz","ID":78945}, {"IsAdult":true,"Name":"Ireylnn","ID":78945}, {"IsAdult":true,"Name":"Isabelle","ID":78945},{"IsAdult":true,"Name":"Vickey","ID":78945}]);
self.PossessionChanges = ko.observableArray([]);
self.PossessionChanges.push(new PossessionChangeVM(self.PossessionChanges().length +1));
self.GetPersonById = function (id) {
return ko.utils.arrayFirst(self.AllFromList, function (person) {
return person.ID === ko.unwrap(id);
});
}
self.AvailableFrom = ko.computed(function() {
var available = ko.utils.arrayFilter(self.AllFromList, function(item) {
return !ko.utils.arrayFirst(self.PossessionChanges() , function (possessionChange) {
var person = self.GetPersonById(possessionChange.SelectedFrom());
if (person) {
return person.ID === item.ID;
} else {
return false;
}
});
});
return available;
});
self.AvailableTo = ko.computed(function() {
var available = ko.utils.arrayFilter(self.AllToList, function(item) {
return !ko.utils.arrayFirst(self.PossessionChanges() , function (possessionChange) {
var person = self.GetPersonById(possessionChange.SelectedTo());
if (person) {
return person.ID === item.ID;
} else {
return false;
}
});
});
return available;
});
self.addPossessionChange = function () {
self.PossessionChanges.push(new PossessionChangeModel(self.PossessionChanges().length + 1));
}
self.removePossessionChangeChange = function(possessionChange) {
self.PossessionChanges.remove(possessionChange);
}
}
function PossessionChangeVM(possessionChangeId) {
var self = this;
self.possessionChangeId = ko.observable(possessionChangeId);
self.SelectedFrom = ko.observable();
self.SelectedTo = ko.observable();
self.ChangeType = ko.pureComputed(function() {
if (self.SelectedFrom() !== undefined && self.SelectedTo() !== undefined) {
return 'Update';
} else if (self.SelectedFrom() === undefined && self.SelectedTo() === undefined) {
return '';
} else if (self.SelectedFrom() === undefined) {
return 'Add';
} else if (self.SelectedTo() === undefined) {
return 'Remove';
} else { return ''; }
});
}
function SelectedPerson(isAdult, name, id) {
var self = this;
self.IsAdult = ko.observable(isAdult);
self.Name = ko.observable(name);
self.ID = ko.observable(id);
}
ko.applyBindings(new BookPossessionTransferVM());
Here's the jsfiddle as well: https://jsfiddle.net/cpd5w9he/11/ If you change person.ID to person, it works, but the options are then incorrect
回答1:
Original problem: When you select an item from the dropdown the value of "SelectedTo" correctly changes to that item's ID value. However, the dropdown options are then recomputed and that item is then removed from the list of options, causing the value of "SelectedTo" to revert back to undefined. You can't have a selected value that isn't part of the options list.
Solution: Here's a snippet that does what you want. I had to pass the root object down into the PossessionChangeVM which I'm not totally happy with, but it works. This way each possessionChangeVM builds its own list of available options using the FullList, and filtering only when the item has been used but is not the current item's selected one.
ko.bindingHandlers.select2 = {
init: function (element, valueAccessor, allBindingsAccessor, bindingContext) {
ko.utils.domNodeDisposal.addDisposeCallback(element,
function() {
$(element).select2('destroy');
});
var select2 = ko.utils.unwrapObservable(allBindingsAccessor().select2);
$(element).select2(select2);
},
update: function (element, valueAccessor, allBindingsAccessor, bindingContext) {
var allBindings = allBindingsAccessor();
if ("value" in allBindings) {
if ((allBindings.select2.multiple || element.multiple) && allBindings.value().constructor != Array) {
$(element).val(allBindings.value().split(',')).trigger('change');
} else {
$(element).val(allBindings.value()).trigger('change');
}
}
$(element).trigger("change");
}
};
function BookPossessionTransferVM() {
var self = this;
self.AllFromList = ([{"IsAdult":false,"Name":"Bob","ID":38438}, {"IsAdult":false,"Name":"Gordon","ID":54686}, {"IsAdult":true,"Name":"Bill","ID":45645}, {"IsAdult":false,"Name":"Sue","ID":1231}, {"IsAdult":false,"Name":"Ling","ID":123578}, {"IsAdult":false,"Name":"Ivy","ID":78945}]);
self.AllToList = ([{"IsAdult":false,"Name":"Adam","ID":38438}, {"IsAdult":false,"Name":"Geoff","ID":54686}, {"IsAdult":true,"Name":"Josh","ID":45645}, {"IsAdult":false,"Name":"Sam","ID":1231}, {"IsAdult":false,"Name":"Ming","ID":123578}, {"IsAdult":false,"Name":"Austin","ID":78945}, {"IsAdult":false,"Name":"Tsz","ID":78945}, {"IsAdult":true,"Name":"Ireylnn","ID":78945}, {"IsAdult":true,"Name":"Isabelle","ID":78945},{"IsAdult":true,"Name":"Vickey","ID":78945}]);
self.PossessionChanges = ko.observableArray([]);
self.UsedTo = ko.computed(function(){
return self.PossessionChanges()
.filter(function(item){
return item.SelectedTo() != undefined;
})
.map(function(item){
return item.SelectedTo();
});
});
self.UsedFrom = ko.computed(function(){
return self.PossessionChanges()
.filter(function(item){
return item.SelectedFrom() != undefined;
})
.map(function(item){
return item.SelectedFrom();
});
});
self.PossessionChanges.push(new PossessionChangeVM(self.PossessionChanges().length +1, self));
self.GetPersonById = function (id) {
return ko.utils.arrayFirst(self.AllFromList, function (person) {
return person.ID === ko.unwrap(id);
});
}
self.addPossessionChange = function () {
self.PossessionChanges.push(new PossessionChangeVM(self.PossessionChanges().length + 1, self));
}
self.removePossessionChangeChange = function(possessionChange) {
self.PossessionChanges.remove(possessionChange);
}
}
function PossessionChangeVM(possessionChangeId, root) {
var self = this;
self.possessionChangeId = ko.observable(possessionChangeId);
self.SelectedFrom = ko.observable();
self.SelectedTo = ko.observable();
self.AvailableFrom = ko.computed(function() {
return ko.utils.arrayFilter(root.AllFromList, function(item) {
return root.UsedFrom().indexOf(item.ID) < 0 || item.ID === self.SelectedFrom();
});
});
self.AvailableTo = ko.computed(function() {
return ko.utils.arrayFilter(root.AllToList, function(item) {
return root.UsedTo().indexOf(item.ID) < 0 || item.ID === self.SelectedTo();
});
});
self.ChangeType = ko.pureComputed(function() {
if (self.SelectedFrom() !== undefined && self.SelectedTo() !== undefined) {
return 'Update';
} else if (self.SelectedFrom() === undefined && self.SelectedTo() === undefined) {
return '';
} else if (self.SelectedFrom() === undefined) {
return 'Add';
} else if (self.SelectedTo() === undefined) {
return 'Remove';
} else { return ''; }
});
}
function SelectedPerson(isAdult, name, id) {
var self = this;
self.IsAdult = ko.observable(isAdult);
self.Name = ko.observable(name);
self.ID = ko.observable(id);
}
ko.applyBindings(new BookPossessionTransferVM());
#tblPossessionChanges {
width: 70%;
height: 100px;
text-align: center;
table-layout: fixed;
}
#tblPossessionChanges td, #tblPossessionChanges th {
padding: 1rem;
}
#tblPossessionChanges thead th {
text-align: center;
}
#tblPossessionChanges thead th:first-child {
text-align: left;
width: 10%;
}
#tblPossessionChanges tbody td:first-child {
text-align: left;
width: 10%;
}
#tblPossessionChanges > tbody > tr > td.prompt > a{
font-weight: bold;
}
#tblPossessionChanges tbody td select{
width: 75%
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.12/js/select2.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.6-rc.0/css/select2.min.css" rel="stylesheet" />
<div>
<table id="tblPossessionChanges">
<thead>
<tr>
<th><a href="#" class="buttonSmall" data-bind="click: addPossessionChange">Add</a></th>
<th>From</th>
<th>To</th>
</tr>
</thead>
<tbody data-bind="foreach: PossessionChanges">
<tr>
<td class="prompt">
<a href="#" class="buttonSmall" data-bind="click: $root.removePossessionChange">Delete</a> </td>
<td>
<select class="form-control"
data-bind="options: AvailableFrom,
value: SelectedFrom,
optionsText: function(i) {return i.Name},
optionsValue: function(i) {return i.ID},
optionsCaption: 'Please select a Person...',
select2: { placeholder: 'Please select a Person...', allowClear: false}"> </select>
</td>
<td>
<select class="form-control"
data-bind="options: AvailableTo,
value: SelectedTo,
optionsText: function(i) {return i.Name},
optionsValue: function(i) {return i.ID},
optionsCaption: 'Please select a Person...',
select2: {placeholder: 'Please select a Person...', allowClear: false}"> </select>
</td>
<td>
<span id="changeTypeSpan" data-bind="text: ChangeType"></span>
</td>
</tr>
</tbody>
</table>
<br/>
</div>
来源:https://stackoverflow.com/questions/59994582/knockout-select2-dropdownlist-contains-correct-options-but-now-displays-nothing