Trying to understand ko.mapping in conjunction with TypeScript and RequireJS. As I understand it, I can create a view model, bind it to a view and expose a complex object to my view via the view model. I am having no luck getting this to work. Most examples on the web want to show how to take a web service response and bind it directly. I am seeking a more basic example than that - I just want to map an unbound object to the screen. I could certainly do it manually, but I think the tool was designed for this exact purpose...
I have two needs:
I have been playing with some sample code as a proof of concept, as the most basic version I could come up with. The idea is to present a view with a button. The text of the button should load with "Hello World!", and when clicked be updated to "Goodbye moon...".
I think my view model needs two objects...
My understanding (which is likely wrong) is that the mapping will take the POJO in and automatically create an observable version of the POJO in the binding object. The view is bound to the binding object. At any time, such as a click of a button, I can augment my POJO, and reload into the binding object and my view will update accordingly.
My View Model is connected as I can set break points and watch them get hit. The loading of the page fails because the bound object is not available. If I change from ko.mapping to standard observables it loads fine.
What am I missing when considering ko.mapping? Is my approach completely flawed?
Basic POJO Class
class DefaultModel {
public myField: string;
}
export = DefaultModel;
View
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>TypeScript HTML App</title>
<script data-main="Application/require-config" src="Scripts/require.js"></script>
</head>
<body>
<h1>TypeScript HTML App</h1>
<button id="myMethodTest" data-bind="text: boundModel().myField, click: function () { myButton_Click() }" ></button>
</body>
</html>
View Model
/// <reference path="../Scripts/typings/knockout/knockout.d.ts" />
/// <reference path="../Scripts/typings/knockout.mapping/knockout.mapping.d.ts" />
import DefaultModel = require("Models/DefaultModel");
import ko = require("knockout");
class DefaultViewModel {
public basicModelInstance: DefaultModel;
public boundModel: any;
constructor() {
// INSTANTIATE THE BOUND MODEL TO BE A BLANK KO MAPPED AWARE OBJECT
this.boundModel = ko.mapping.fromJS({});
// SETUP A BASIC INSTANCE OF A POJO
this.basicModelInstance = new DefaultModel;
this.basicModelInstance.myField = "Hello World!";
// LOAD THE POPULATED POJO INTO THE BOUND OBVSERVABLE OBJECT
this.boundModel = ko.mapping.fromJS(this.basicModelInstance, {}, this.boundModel);
}
myButton_Click() {
// UPDATE THE POJO
this.basicModelInstance.myField = "Goodbye Moon...";
// RELOAD THE POJO INTO THE BOUND OBJECT
this.boundModel = ko.mapping.fromJS(this.basicModelInstance, {}, this.boundModel);
}
}
export = DefaultViewModel;
RequireJS Configuration
/// <reference path="../Scripts/typings/requirejs/require.d.ts" />
require.config({
baseUrl: "",
paths: {
"jQuery": "Scripts/jquery-2.1.1",
"knockout": "Scripts/knockout-3.2.0.debug",
"utilities": "Application/utilities",
"ViewModelMapper": "Application/ViewModelMapper",
"komapping": "Scripts/knockout.mapping-latest.debug"
},
shim: {
"jQuery": {
exports: "$"
},
komapping: {
deps: ['knockout'],
exports: 'komapping'
}
},
});
require(["jQuery"], function ($) {
$(document).ready(function () {
// alert('dom ready');
require(["utilities", "knockout", "ViewModelMapper", "komapping"], (utilities, knockout, viewModelMapper, komapping) => {
utilities.defineExtensionMethods($);
knockout.mapping = komapping;
var url = window.location;
var location = utilities.getLocation(url);
var urlPath = location.pathname;
var urlPathWithoutExtension = urlPath.replace(".html", "");
var viewModel = viewModelMapper.getViewModel(urlPathWithoutExtension);
knockout.applyBindings(viewModel);
});
});
});
A) In your View Model code, the call to ko.mapping.fromJS
only needs the first two parameters. Since it returns the bound model, you don't need to pass in your bound model. It should be:
this.boundModel = ko.mapping.fromJS(this.basicModelInstance, {});
B) viewModel.boundModel
is not a function, it's an object. So in your html, your binding text: boundModel().myField
should be text: boundModel.myField
C) You are misunderstanding the way the binding is supposed to work. Once you have your bound model, there is no need to update the "POJO" and then recreate your bound model every time something in your view model changes. The two-way data binding that knockout offers will keep your view model and your ui (html) in sync, and so you only have to work with your view model from then on. When you need to take what's in your view model and update your "POJO", which should only be when you need to update the server, you can use the ko.mapping.toJS
function which does the opposite of the ko.mapping.fromJS
function. You pass in your bound model and it will give you back the vanilla JS "POJO" object, removing all of the observables.