Search code examples
javascriptjqueryknockout.jsknockout-mapping-pluginknockout-3.0

Value not bound to Ko.observable in knockout.js


This is the code I use to bind to a textbox:

var CategoryViewModel = {
    categoryModel: ko.observable({
        categoryId: ko.observable(),
        categoryName: ko.observable(),
        active: ko.observable()
    }),
    GetCategoryById: function (data, event) {
        CategoryViewModel.ClickId(event.target.id);
        var ajaxUrl = ApplicationRootUrl("GetCategoryById", "Category") + "/" + CategoryViewModel.ClickId();

        $.ajax({
            type: "GET",
            contentType: "application/json; charset=utf-8",
            url: ajaxUrl,
            dataType: "json",
            success: function (data) {
                if (data.isSuccess) {
                    // This value got bind to textbox
                    CategoryViewModel.categoryModel(data.data);
                } 
            },
            error: function (err) {

            }
        });
    },
    CategoryAddButton: function() {
        CategoryViewModel.categoryModel();
        $('#addCategoryModel').modal('toggle');
    }            
};

$(document).ready(function () {
    ko.applyBindings(CategoryViewModel, document.getElementById("categoryMain"));
});

The CategoryAddButton method is called on button click. I am trying to empty the model value in this method.

Here is the HTML:

<input type="text" name="CategoryName" class="form-control" placeholder="Enter Category Name" data-bind="textinput: categoryModel().categoryName">

The textbox value gets bound on ajax call. However, after the CategoryAddButton method is called, the value does not get bound to the textbox.


Solution

  • First, I'd advise you using a different approach to creating viewmodels from what you've written. Even though some beginner's examples do the same, that's only for simplicity - in reality it is often not a good idea to create viewmodels as object literals. This is because as posted here, here, and here among many other duplicates, accessing another property of the same object can get pretty dirty despite how trivial that task should be.

    So, to get over this problem, you should use constructors and the new operator instead, because that enables you to manipulate the object significantly easier. However, I've added this only as a guide to you to write cleaner code, using constructors and new object syntax won't solve the problem alone.

    So let's get back to your question. To find out the reason why your code does not work, look how you manipulate your data when the binding works and how when it doesn't.

    You said that after the AJAX-call had succeeded the value got updated properly so the binding worked. This is because in the success callback of the AJAX-call, you are actually passing an object into categoryModel. However, I'd point out that what you pass to it is not an observable but just a plain object, whereas you initially create it with its properties being observables! So even there you might run into problems.

    You also said that after the button had been clicked, the value was not updated. I am not really sure what you even want to achieve here; what do you want to be displayed and where should the data come from? Because this line of code you've written:

    CategoryViewModel.categoryModel();
    

    is simply a getter -- it won't alter the object in any way, you are just reading its value. Without actually modifying it, of course nothing will change.

    So, I'll give you a possible way of implementing the whole thing, and I suggest you read more on javascript object constructors and how to use knockout properly using them.

    function categoryViewModel() {
        var self = this;
    
        // Don't wrap these in another observable, that's totally needless.
        this.categoryId = ko.observable(null);
        this.categoryName = ko.observable(null);
        this.active = ko.observable(true);
    
        // You could actually define this on the prototype
        // but I don't want to make it even more complicated
        this.GetCategoryById = function(categoryId) {
            // You could do this if the value passed can either be a plain value 
            // or an observable; ko will return the actual value to you.
            var catId = ko.utils.unwrapObservable(categoryId);
    
            var ajaxUrl = ApplicationRootUrl("GetCategoryById", "Category") + "/" + catId;
    
            $.ajax({
                type: 'GET',
                contentType: "application/json; charset=utf-8",
                url: ajaxUrl,
                dataType: "json",
                success: function(data) {
                    if (data.isSuccess) {
                        // Correct the object references according to
                        // the structure of the response if needed.
                        self.categoryId(data.data.id);
                        self.categoryName(data.data.name);
                        self.active(data.data.isActive);
                    }
                },
                error: function(err) {
                }
            });
        };
    
        this.CategoryAddButton = function() {
            self.categoryId(null);
            self.categoryName(null);
            self.isActive(true); // If you want to default to true.
            $('#addCategoryModel').modal('toggle');
        };
    };
    
    $(document).ready(function () {
        ko.applyBindings(new categoryViewModel(), document.getElementById("categoryMain"));
    });
    

    And your HTML could be:

    <input type="text" name="CategoryName" class="form-control" data-bind="textInput: categoryName" />
    

    As for the GetCategoryById function, it would even be nicer if you assigned that to the function prototype, rather than assigning a "copy" to each and every object created. But since I assume you'll only ever have 1 instance of your viewmodel, I'll consider that out of scope for now.