1
votes

I have this UI

enter image description here

And my goal is to update table after clicking Update button based on Id and Description parameters

I have following code

Html

<table class="table table-bordered table-striped table-hover">
    <thead>
        <tr>
            <th>Id</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody>
        @foreach (var item in Model.Products)
        {
            <tr>
                <td>@item.Id</td>
                <td>@item.Description</td>
            </tr>
        }
    </tbody>
</table>

<label>Id</label>
<input data-bind="value: productId" type="text" class="form-control" />

<br />

<label>Description</label>
<input data-bind="value: productDescription" type="text" class="form-control" />

<br />

<button class="btn btn-primary" data-bind="click: update">Update</button>

JavaScript

<script type='text/javascript'>

            // Plain javascript alternative to jQuery.ready.
            (function () {

                // Convert server model to json object.
                var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};

                function viewModel()
                {
                    var index;

                    // Store default this to self, to be able use self in subfunctions.
                    var self = this;

                    // Product array from server.
                    self.products = json.products;

                    // Automatic refreshed parameter in UI.
                    self.categoryName = ko.observable(json.categoryName);

                    // Update form parameters.
                    self.productId = null;
                    self.productDescription = null;

                    // Update function.
                    self.update = function () {

                        // Get products collection index.
                        index = self.products.findIndex(function (product) {
                            return product.Id == self.productId
                        });

                        // Throws -1, index was not found !
                        alert(index);

                        // Assign new value.
                        // self.products[index].description(self.productDescription);
                    };
                }

                // Apply viewModel to UI
                ko.applyBindings(new viewModel());

            })();

    </script>

I need help with two things

  1. Update knockout viewmodel (self.products collection based on Id and Description inputs in viewModel.Update function)
  2. Update table (I don't know how to bind the rows, when using razor foreach)

EDIT:

When I change Razor loop with Knockout foreach

<tbody data-bind="foreach: products">
        <tr>
            <td data-bind="text: id"></td>
            <td data-bind="text: description"></td>
        </tr>
    </tbody>

it does not work either :/ I tried some changes with observables

 <script type='text/javascript'>

            // Plain javascript alternative to jQuery.ready.
            (function () {

                // Convert server model to json object.
                var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};

                function viewModel()
                {
                    var index;

                    // Store default this to self, to be able use self in subfunctions.
                    var self = this;

                    // Product array from server.
                    self.products = ko.observable(json.products);

                    // Automatic refreshed parameter in UI.
                    self.categoryName = ko.observable(json.categoryName);

                    // Update form parameters.
                    self.productId = ko.observable();
                    self.productDescription = ko.observable();

                    // Update function.
                    self.update = function () {

                        self.products()[self.productId()-1].description = self.productDescription()    
                    };
                }

                // Apply viewModel to UI
                ko.applyBindings(new viewModel());

            })();

    </script>

I do not get an error, but the value is not changed in the html table.

1
The razor loop only exists on the server so to get that to update you either need to submit the form back to the server to re-render, or you should replace the razor loop with a knockout loop on the front-end. - Jason Spake
Cannot i have both, rendered knockout attributes with razor ? I can render same html on server as client will. Anyway if i will use knockout loop, how to update items in collection not immediately but after click event (because inputs has to be validated and stored first) ? - Muflix
You can add knockout bindings with razor sure, but the problem is context. If you don't have a knockout-loop knockout doesn't know which row you're on and won't know what to apply those bindings to. Every binding will be attempting to bind to the root view-model. Now you could add a bunch of other data-attributes to give each row an index and use some jquery to find the row with the matching attribute and update it manually, but then you might as well not be using knockout. - Jason Spake
It looks like in your previous question [stackoverflow.com/questions/50081008/… you had a knockout loop that was working correctly in the end. Why did you decide to replace it with a razor loop? - Jason Spake
I was just curious, if I am not missing something. Razor has some advantages i wanted to use with knockout, but if its not compatible i can use knockout only. - Muflix

1 Answers

0
votes

Your knockout code looks mostly correct but your UI isn't updating because the values that you're changing aren't observable. If you want the changes to the items to reflect in the table you need to make each field that will update an observable instead of having a single observable property for the entire json object.

The following code will only update if the entire object reference changes. IE if you fill self.products with a new object. It will not update if you change a property on the existing object.

// Product array from server.
self.products = ko.observable(json.products);

You'll need to change your code to individually apply observable properties to your json object when it comes from the server.

// Product array from server.
self.products = ko.observableArray([]); //Changed to array type
for(var i = 0; i < json.products.length; i++){
    self.products.push({
        id: json.products[i].id,
        description: ko.observable(json.products[i].description)
    });
}

And then you'll have to update the value using the function style getter/setter.

// Update function.
self.update = function () {
    self.products()[Number(self.productId())-1].description(self.productDescription());
};

  // Convert server model to json object.
  var json = {"products":[{"id":1,"description":"Product A"},{"id":2,"description":"Product B"},{"id":3,"description":"Product C"}],"categoryName":"chocolates"};

  function viewModel()
  {
    var index;

    // Store default this to self, to be able use self in subfunctions.
    var self = this;

    // Product array from server.
    self.products = ko.observableArray([]); //Changed to array type
    for(var i = 0; i < json.products.length; i++){
        self.products.push({
            id: json.products[i].id,
            description: ko.observable(json.products[i].description)
        });
    }

    // Automatic refreshed parameter in UI.
    self.categoryName = ko.observable(json.categoryName);

    // Update form parameters.
    self.productId = ko.observable();
    self.productDescription = ko.observable();

    // Update function.
    // Update function.
    self.update = function () {
        self.products()[Number(self.productId())-1].description(self.productDescription());
    };
    
  }

  // Apply viewModel to UI
  ko.applyBindings(new viewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>

<table class="table table-bordered table-striped table-hover">
    <thead>
        <tr>
            <th>Id</th>
            <th>Description</th>
        </tr>
    </thead>
    <tbody data-bind="foreach: products">
        <tr>
            <td data-bind="text: id"></td>
            <td data-bind="text: description"></td>
        </tr>
    </tbody>
</table>
<br/>

<label>Id: </label>
<input data-bind="value: productId" type="text" class="form-control" />

<br />

<label>Description: </label>
<input data-bind="value: productDescription" type="text" class="form-control" />

<br />

<button class="btn btn-primary" data-bind="click: update">Update</button>