Knockout.js Update ViewModel on button click

14,296

Solution 1

Well. Final solution based on @GoTo answer is: Here is the way to call function in viewmodel via click databind.

<button type="button" class="btn btn-default" id="7" value="7" data-bind="click: getDays.bind($data, '7')">7</button>

Here is the function. As you can see I'm calling self.salesdata instead of viewModel. This solution is working fine but somehow now I have problem with data format that is binded this way - <td data-bind="text: moment($data.whensold).format('DD.MM', 'ru')"></td>.

 self.getDays = function (days) {
                    var uri = "/api/sales?days=" + days;
                    $.getJSON(uri, function (data) {
                        ko.mapping.fromJS(data.$values, {}, self.salesdata);
                    });
                }

Solution 2

I'm new to all of this, but if I'm reading your code correctly, you are calling the request function on a new instance of the view model and not the one that was bound to the html document. You need to make the request call on the view model that you created after the initial get call completed.

Update: Sorry, I should have been more specific about the code I was referring to. At the end of your code block you have the following code:

    $(".btn-default").click(function () {
        days = $(this).val();
        var uri = "/api/sales?days=" + days;     
        new viewModel().request(uri);
    });

In this code, it appears that each time the default button is clicked, a new view model is created and the request function is called on that view model.

In the document ready function where you are defining what happens after the sales data is loaded, you have the following code which is what creates the view model that the html document is actually bound to:

    var vm = new viewModel();
    vm.salesdata(data.$values);
    ko.applyBindings(vm);

Nothing ever calls the request function on this view model. I wonder if what you really want is to somehow bind the request function in this view model to the default button.

Solution 3

I would try updating the viewmodel salesdata observable, by giving context: self and using the following success method:

self.request = function (uri) {
        $.ajax({
            url: uri,
            contentType: 'application/json',
            context: self,
            data: [],
            type: 'GET',
            cache: false,
        success: function (data) {
            this.salesdata(data.$values);
        }
        });
    }

EDIT:

I can see you attached a click event with jQuery. You should use knockout clck binding instead:

<button data-bind="click: clickEvent" value="1">Click me!</button>

And in the viewmodel

clickEvent: function (data, event) { 
    days = event.target.value;
    var uri = "/api/sales?days=" + days;     
    data.request(uri);
}

This way you can retrieve your viewmodel instead of creating a new one as you did with new viewModel().request(uri);

For more on click binding see http://knockoutjs.com/documentation/click-binding.html

Solution 4

Building slightly on the answer from @brader24 here:

In your update button's click event, you use this line of code:

new viewModel().request(uri);

What that is doing is creating a new viewModel (separate from the one that you already have instantiated and have applied bindings for) and filling it's observable array with data via your request function. It isn't affecting your original viewModel at all (the one that has it's bindings applied on the DOM!). So you won't see any errors, but you also won't see anything happening on the page because all you did was create a new viewModel in memory, fill it with data, and do nothing with it.

Try this code (everything in your viewModel function looks fine).

$(document).ready(function () {
    var vm = new viewModel(); // declare (and instantiate) your view model variable outside the context of the $.ajax call so that we have access to it in the click binding
    $.ajax({
        url: "/api/sales",
        type: "GET",
        cache: false,
    }).done(function (data) {
        vm.salesdata(data.$values);
        ko.applyBindings(vm);
    }).error(function (xhr, status, error) {
        var err = eval("(" + xhr.responseText + ")");
        alert(err.Message);
    });
    //Here i'm calling for ViewModel update
    $(".btn-default").click(function () {
        days = $(this).val();
        var uri = "/api/sales?days=" + days;     
        vm.request(uri); // don't use a new instance of a view model - use the one you have already instantiated
    });
});

Using a Knockout click binding instead of attaching a click event handler using jQuery is usually the recommended route, but it is not necessary - so your existing code (with the modifications above) should work fine. For more info on that, see Using unobtrusive event handlers in the Knockout docs

Share:
14,296
andrey.shedko
Author by

andrey.shedko

Updated on June 30, 2022

Comments

  • andrey.shedko
    andrey.shedko almost 2 years

    Well, that's not the best situation description... Anyway, I'm trying to update my ViewModel but it's not working. By default I'm getting data from controller function and by button click - from another function in same contoller, but ViewModel contain only data received after first ViewModel initialization.

    <script>
         function viewModel () {
            var self = this;
            self.currentPage = ko.observable();
            self.pageSize = ko.observable(10);
            self.currentPageIndex = ko.observable(0);
            self.salesdata = ko.observableArray();
            self.newdata = ko.observable();
            self.currentPage = ko.computed(function () {
                var pagesize = parseInt(self.pageSize(), 10),
                startIndex = pagesize * self.currentPageIndex(),
                endIndex = startIndex + pagesize;
                return self.salesdata.slice(startIndex, endIndex);
            });
            self.nextPage = function () {
                if (((self.currentPageIndex() + 1) * self.pageSize()) < self.salesdata().length) {
                    self.currentPageIndex(self.currentPageIndex() + 1);
                }
                else {
                    self.currentPageIndex(0);
                }
            }
            self.previousPage = function () {
                if (self.currentPageIndex() > 0) {
                    self.currentPageIndex(self.currentPageIndex() - 1);
                }
                else {
                    self.currentPageIndex((Math.ceil(self.salesdata().length / self.pageSize())) - 1);
                }
            }
            //Here I'm trying to update ViewModel
            self.request = function (uri) {
                $.ajax({
                    url: uri,
                    contentType: 'application/json',
                    data: [],
                    type: 'GET',
                    cache: false,
                success: function (data) {
                    ko.mapping.fromJS(data.$values, {}, self.salesdata);
                }
                });
            }
        }
        $(document).ready(function () {
            $.ajax({
                url: "/api/sales",
                type: "GET",
                cache: false,
            }).done(function (data) {
                var vm = new viewModel();
                vm.salesdata(data.$values);
                ko.applyBindings(vm);
            }).error(function (xhr, status, error) {
                var err = eval("(" + xhr.responseText + ")");
                alert(err.Message);
            });
            //Here i'm calling for ViewModel update
            $(".btn-default").click(function () {
                days = $(this).val();
                var uri = "/api/sales?days=" + days;     
                new viewModel().request(uri);
            });
        });
    </script>
    

    UPDATE. I chaged block of code where I'm getting new data to be as follow:

    self.request = function (uri) {
                    $.getJSON(uri, function (data) {
                        ko.mapping.fromJS(data.$values, {}, viewModel);
                    });
                }
    

    Unfortunately this is not working as well. Here is no any JS errors, controller return proper portion of updated data.