Search code examples
jqueryajaxknockout.jsknockout-mapping-plugin

Using jquery ajax with knockout.js mapping plugin


I am trying to use jquery's $.ajax function with the knockout.js mapping plugin, using the test api here: http://rest.learncode.academy/

The data coming back is not in the right format. My js looks like this:

    function FromDb(){

             $.ajax({
              type: 'GET',
              url: 'http://rest.learncode.academy/api/johnbob/friends',
              //dataType: "json",
              success: function(data) {
                console.log("I have friends!", data[0]); //returns all of johnbob's friends
                myResult = data[0];
            return myResult
                }
            });



//return{ name: 'test' }

    }


    function MaptoKo(frnd){
        var map = ko.mapping.fromJS(frnd);
        return map;
    }

    var obj={
        friends:ko.observable(new MaptoKo(new FromDb())),
    }

    ko.applyBindings(obj.friends);

Because the api returns an array, and I only need a single object I am using the data[0] to just get the first item in the array. When I comment out the ajax call and uncomment the //return{ name: 'test' }, I get the result that I want, Im just not sure why it isnt working using the ajax call.


Solution

  • Took me a few minutes to remember how to do this the right way, which is to update your bindings from inside the AJAX success callback function.

    The problem is the disconnect between the call to FromDb() and the AJAX success function. Returning the value from the success function happens asynchronously, meaning there is no way for the code to know to hook the value returned back into the MaptoKo(new FromDb()) call. When you make the FromDb() call just return { name: 'test' };, that's happening synchronously, so the result does make it into the MaptoKo function.

    $.getJSON("/some/url", function(data) { 
        // Now use this data to update your view models, 
        // and Knockout will update your UI automatically 
    })
    

    So, all Knockout needs to help you do is: ...

    • For loading, update your view model using data that you’ve received using one of the above techniques

    Source: Knockout JSON Data Docs

    For your specific code, that would mean something like the following:

    function FriendsViewModel() {
        var self = this;
        self.friends = ko.observable();
    
        // Load the initial data
        $.ajax({
            type: 'GET',
            url: 'http://rest.learncode.academy/api/johnbob/friends',
            //dataType: "json",
            success: function(data) {
                //returns all of johnbob's friends
                console.log("I have friends!", data[0]);
                myResult = data[0];
                // Don't return the result, bind it to the viewmodel
                self.friends(myResult);
              }
          });
    }
    
    ko.applyBindings(new FriendsViewModel());
    

    This is well covered in the Loading and Saving tutorial. Note that this no longer really makes use of the mapping plugin. To tie that in, following the advice of the mapping plugin docs:

    To create a view model via the mapping plugin, replace the creation of viewModel in the code above with the ko.mapping.fromJS function:

    var viewModel = ko.mapping.fromJS(data);
    

    This automatically creates observable properties for each of the properties on data. Then, every time you receive new data from the server, you can update all the properties on viewModel in one step by calling the ko.mapping.fromJS function again:

    // Every time data is received from the server:
    ko.mapping.fromJS(data, viewModel);
    
    $.(function (){
    
        // Load the initial data
        $.ajax({
            type: 'GET',
            url: 'http://rest.learncode.academy/api/johnbob/friends',
            //dataType: "json",
            success: function(data) {
                //returns all of johnbob's friends
                console.log("I have friends!", data[0]);
                myResult = data[0];
                // Don't return the result, bind it to the viewmodel
                var viewModel = ko.mapping.fromJS(myResult);
                // Only bind once after the initial load.
                // If you save or reload data, don't reapply bindings
                ko.applyBindings(viewModel);
              }
          });
    });
    

    That last example is just a guess, I've never used the mapping plugin myself. But hopefully it illustrates that you can't just return results from async callbacks.