Reputation: 13
Here is my HTML code:
<!-- ko foreach: warehouseOrders().StoreOrders -->
<tr>
<td>
<b data-bind="text: Product"></b>
</td>
<td>
<b data-bind="text: Store"></b>`enter code here`
</td>
<td>
<b data-bind="text: Quantity"></b>
</td>
<td>
<select data-bind=" value: OrderStatusId , options: $parent.orderStatuses(), optionsText: 'Status', optionsValue: 'Id'"></select>
<b data-bind="text: OrderStatusId"></b>
</td>
</tr>
<!--/ko-->
So StoreOrders contains all properties : Product, Store,Quantity,OrderStatusId.
The specific problem: select options: $parent.orderStatuses()
contains a list of orderstatuses(Id,Status). I set text and value properties for the select. All works fine, when selecting an option it is written into OrderStatusId property of the object, but initially, OrderStatusId contains a value, which knockout should read and set in the select. So when i open up select should be on that value.
Here are the models:
Main class:
public class WarehouseDTO
{
public System.Guid Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public List<StoreOrderDTO> StoreOrders { get; set; }
public List<StoreWarehouseDTO> StoreWarehouses { get; set; }
public List<WarehouseProductDTO> WarehouseProducts { get; set; }
}
and the StoreOrderDTO
public class StoreOrderDTO
{
public System.Guid Id { get; set; }
public string Product { get; set; }
public string Store { get; set; }
public string Warehouse { get; set; }
public int Quantity { get; set; }
public System.Guid OrderStatusId { get; set; }
}
Javascript:
self.warehouseOrdersRequest = function (item) {
ajaxHelper('/api/OrderStatus', 'GET').done(function (data) {
self.orderStatuses(data);
});
self.warehouseOrders(item);
}
Here item represents 1 object of the type WarehouseDTO
, and OrderStatuses
is given an array of objects {Id,Status}
full code of the table
<!--ko if : warehouseOrders()-->
<div>
<div class="col-md-4">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><b data-bind="text: warehouseOrders().Name"></b> - Orders</h2>
</div>
<table class="table">
<tr>
<th>
@Html.DisplayName("Product")
</th>
<th>
@Html.DisplayName("Store")
</th>
<th>
@Html.DisplayName("Quantity")
</th>
<th>
@Html.DisplayName("Order Status")
</th>
</tr>
<!-- ko foreach: warehouseOrders().StoreOrders -->
<tr>
<td>
<b data-bind="text: Product"></b>
</td>
<td>
<b data-bind="text: Store"></b>
</td>
<td>
<b data-bind="text: Quantity"></b>
</td>
<td>
<select data-bind=" value: OrderStatusId , options: $parent.orderStatuses(), optionsText: 'Status', optionsValue: 'Id'"></select>
<b data-bind="text: OrderStatusId"></b>
</td>
</tr>
<!--/ko-->
</table>
</div>
<a href="#" data-bind="click: closeWarehouseOrders" class="btn btn-defaul">Cancel</a>
<a href="#" data-bind="click: saveWarehouseOrders" class="btn btn-defaul">Save</a>
</div>
</div>
<!--/ko-->
I noticed something, that after I select an item, and its written into OrderStatusId the <b data-bind="text: OrderStatusId"></b>
is displayed(it didn`t before), and yes, im sure there is a value in it.
Upvotes: 1
Views: 1107
Reputation: 23372
Because the orderStatuses
are loaded async, while the current state isn't, knockout cannot correctly initialize the <select>
data-binds here:
<select data-bind=" value: OrderStatusId , options: $parent.orderStatuses(), optionsText: 'Status', optionsValue: 'Id'"></select>
At the time of binding, $parent.orderStatuses()
is an empty array. OrderStatusId
is a string. There are no options to look for the option.Id == value
that is used to set the initial value.
When updating the observable options array, knockout apparently does not retry to match the currently selected value to one of the new options. (citation needed, I tried to look up the source to back this up but couldn't find it quickly enough...)
One way you could fix this is by preventing initialization of the <select>
when the statuses
haven't yet loaded:
<!-- ko if: orderStatuses().length -->
<select data-bind="options: orderStatuses"></select>
<!-- /ko -->
You can see this fix in the example below. When you remove the virtual if
binding element, you'll see the initial status is no longer set. Also, when you swap the order of setItem
and setStatuses
, you'll notice you no longer need the extra if-bind.
var vm = {
item: ko.observable(),
statuses: ko.observableArray([])
}
ko.applyBindings(vm);
var setItem = function() {
vm.item({
orders: ko.observableArray([{
text: "Item 1",
status: ko.observable(0)
}, {
text: "Item 2",
status: ko.observable(1)
}
])
});
}
var setStatuses = function() {
vm.statuses([{
label: "Status 0",
id: 0
}, {
label: "Status 1",
id: 1
}]);
}
setItem();
setStatuses();
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<!-- ko if: item -->
<ul data-bind="foreach: item().orders">
<li>
<div data-bind="text: text"></div>
<!-- ko if: $parent.statuses().length -->
<select data-bind="options: $parent.statuses, value: status, optionsText: 'label', optionsValue: 'id'"></select>
<strong data-bind="text: 'status: ' + status()"></strong>
<!-- /ko -->
</li>
</ul>
<!-- /ko -->
Upvotes: 1