Lorincz Patrik
Lorincz Patrik

Reputation: 13

Knockout.js select data-bind value is not read but is written

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

Answers (1)

user3297291
user3297291

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

Related Questions