Search code examples
jsonknockout.jsknockout-mapping-plugin

knockoutjs throws "Unknown template value" when using knockout.mapping


I'm trying to use Knockout.mapping to make it easier to bind JSON objects. I use requirejs to load the library into my code, but whenever I do this I get this error:

Error: Component 'album-details': Unknown template value: [object Object]

This is the content of the component:

define(['knockout', 'knockout-mapping', 'text!./album-details.html'], function (ko, templateMarkup) {
    function AlbumDetails(params) {

        var self = this;
        self.album = {};

        $.getJSON('http://localhost:62755/api/album/' + self.id, function (r) {
            ko.mapping.fromJS(r, self.album);
        });        
  }

  // This runs when the component is torn down. Put here any logic necessary to clean up,
  // for example cancelling setTimeouts or disposing Knockout subscriptions/computeds.
  AlbumDetails.prototype.dispose = function() { };

  return { viewModel: AlbumDetails, template: templateMarkup };

});

And this is my require.config.js

// require.js looks for the following global when initializing
var require = {
    baseUrl: ".",
    paths: {
        "bootstrap":            "bower_modules/components-bootstrap/js/bootstrap.min",
        "crossroads":           "bower_modules/crossroads/dist/crossroads.min",
        "hasher":               "bower_modules/hasher/dist/js/hasher.min",
        "jquery":               "bower_modules/jquery/dist/jquery",
        "knockout":             "bower_modules/knockout/dist/knockout",
        "knockout-projections": "bower_modules/knockout-projections/dist/knockout-projections",
        "knockout-mapping":     "bower_modules/knockout-mapping/knockout.mapping",
        "signals":              "bower_modules/js-signals/dist/signals.min",
        "text":                 "bower_modules/requirejs-text/text"
    },
    shim: {
        "bootstrap": { deps: ["jquery"] },
        "knockout-mapping": {deps: ["knockout"] }
    }
};

And this is a sample JSON response:

{
    "$id" : "1",
    "id" : 14,
    "name" : "Bound to pretend",
    "artist" : {
        "$id" : "2",
        "id" : 12,
        "name" : "Velvet Veins",
        "musicBrainzId" : "f7a48e35-7891-4277-bf19-a1ebeeffea33"
    },
    "releaseDate" : "2014-01-01T00:00:00Z",
    "lastFetchDate" : "2015-04-27T13:31:41Z",
    "musicBrainzReleaseId" : "ea25f9f1-1f26-473d-b084-cf961ef126cf",
    "tracks" : {
        "$id" : "3",
        "$values" : [{
                "$id" : "4",
                "id" : 173,
                "position" : 1,
                "name" : "Boud to pretend"
            }, {
                "$id" : "5",
                "id" : 174,
                "position" : 2,
                "name" : "Arizona ghost"
            }, {
                "$id" : "6",
                "id" : 175,
                "position" : 3,
                "name" : "Sweet heat"
            }, {
                "$id" : "7",
                "id" : 176,
                "position" : 4,
                "name" : "Opal"
            }, {
                "$id" : "8",
                "id" : 177,
                "position" : 5,
                "name" : "Nation sack"
            }, {
                "$id" : "9",
                "id" : 178,
                "position" : 6,
                "name" : "Reptiles of the shore"
            }
        ]
    }
}

I have used yeoman to scalfold and bower to install the libraries. I'm not sure how to troubleshoot this as the error describes [object Object] it's not very easy for me to understand what is the cause of the error.

From the network trace I can see that knockout.mapping.js is retrieved by the browser, but the ajax request to the API isn't.

How can I troubleshoot this? Is there anything obviously wrong in my code?


Solution

  • You are not correctly including the mapping plugin in your module declaration because you are missing a parameter for the mapping plugin. So it passes the mapping plugin itself in the templateMarkup parameter and KO will complain when it finds it in the template: templateMarkup.

    To fix this you need to add an argument before the templateMarkup which will hold the mapping plugin

    define(['knockout', 'knockout-mapping', 'text!./album-details.html'], 
        function (ko, mapping, templateMarkup)
    

    Because you are using RequireJS the mapping plugin is not available on the ko.mapping property but you need to use the second argument mapping to access its functionality.

    So you need to change also your code where you do the mapping to:

    $.getJSON('http://localhost:62755/api/album/' + self.id, function (r) {
        mapping.fromJS(r, self.album);
    });