I am attempting use the KnockoutJS SPA Yeoman generator to build a SPA app and use the ko.mapping plugin. I've adjusted the require.config.js to include ko-mapping.
In a standalone app I can get the mapping plugin to work just fine, but when I try to use it within the constructed function of the component the bindings are never available in the template html. Any ideas on what I might be doing wrong to get mapping plugin to play within KO component?
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",
"komapping": "bower_modules/knockout-mapping/knockout.mapping",
"knockout-projections": "bower_modules/knockout-projections/dist/knockout-projections",
"signals": "bower_modules/js-signals/dist/signals.min",
"text": "bower_modules/requirejs-text/text"
},
shim: {
"bootstrap": { deps: ["jquery"] },
"komapping": { deps: ["knockout"], exports: 'komapping'}
}
};
define(['jquery', 'knockout', './router', 'bootstrap', 'knockout-projections'], function($, ko, router) {
// Components can be packaged as AMD modules, such as the following:
ko.components.register('nav-bar', { require: 'components/nav-bar/nav-bar' });
ko.components.register('home-page', { require: 'components/home-page/home' });
// ... or for template-only components, you can just point to a .html file directly:
ko.components.register('about-page', {
template: { require: 'text!components/about-page/about.html' }
});
ko.components.register('bems', { require: 'components/bems/bems' });
ko.components.register('user-page', { require: 'components/user-page/user' });
ko.components.register('team-page', { require: 'components/team-page/team' });
ko.components.register('exec-page', { require: 'components/exec-page/exec' });
// [Scaffolded component registrations will be inserted here. To retain this feature, don't remove this comment.]
// Start the application
ko.applyBindings({ route: router.currentRoute });
});
define(['knockout', 'text!./bems.html','komapping'], function(ko, templateMarkup, komapping) {
function Bems(params) {
ko.mapping = komapping;
var self = this;
self.response = ko.observable();
if (typeof params[0] != 'undefined') {
// Suppress search while loading
document.getElementById("bems-input").style.visibility = "hidden";
self.bemsId = String(params[0]);
$.ajaxSetup({
cache: false,
"error":function() {
document.getElementById("bems-input").style.visibility = "visible";
window.location = ("#/bems/");
}
});
$.getJSON('/app/skyline/api/bems/' + self.bemsId, function(ajax_response) {
document.getElementById("bems-input").align = "right";
document.getElementById("bems-input").style.visibility = "visible";
document.getElementById("bems-container").style.visibility = "visible";
ko.mapping.fromJS(ajax_response, {}, self.response);
});
}
// Nothing to do without bemsId parameter
}
return { viewModel: Bems, template: templateMarkup };
});
I also tested with the following change:
self.response = ko.observable();
self.message = ko.observable("It works");
$.getJSON('/app/skyline/api/bems/' + self.bemsId, function(ajax_response) {
self.response = ko.mapping.fromJS(ajax_response);
In the above the self.message binds properly and can be accessed in the template. However the mapped attributes in self.response are not bound.
The key difference between my working setup and the broken case is in the working example I set the ko.mapping result to the entire view model variable where as here the function Bems is the view model and this is a variable under the vm.
I took another look at this today and getting close to the problem. Looks like the primary issue is the order of operations between the AJAX callback being fired and the binding occurring through the Yoeman component app framework. If I do the following and put a 2 second delay in the main ViewModel it manages to load the binding contents for the first bound attribute in the view but then dies anyway with TypeError even though the data did load for attribute. This halts the script and no other attributes load.
require.js:900 TypeError: Unable to process binding "text: function (){return response().engagement_list()[0].CASE_TITLE }"(…)
function BemsViewModel(params) {
self.response = ko.observable();
$.getJSON('/app/skyline/api/bems/' + self.bemsId(), function(ajax_response) {
$("#bems-input").attr("align","right");
$("#bems-input").attr("style", "visibility: visible")
$("#bems-container").attr("style", "visibility: visible")
var foobar = {};
ko.mapping.fromJS(ajax_response, {}, foobar);
self.response(foobar);
Is there any best practice/pattern to ensure binding occurs only after all bits of the view model are loaded using this KO component SSA template? Any help/ideas would be appreciated. I really don't want to manually define the entire view model...
Ok, I have this working now. The key is to do the above where you properly add in mapped attributes into the existing view model but must also use the data-bind with attribute to map this dynamic content. This way the ko.binding function only digs in as far as the top level and doesn't complain about missing attributes below it. As these load asyc after the viewmodel already is bound this data will fill in after the fact.
http://knockoutjs.com/documentation/with-binding.html
<div id="bems-dyn" data-bind="with: response">
<p>BEMS Title: <span data-bind="text: engagement_list()[0].CASE"></span></p>
<p>Container Type: <span data-bind="text: global_params.container_type"></span></p>
<p>BEMS Id: <span data-bind="text: global_params.bems_id"></span></p>
</div>
self.response = ko.observable();
$.getJSON('/app/test/api/bems/' + self.bemsId(), function(ajax_response) {
$("#bems-input").attr("align","right");
$("#bems-input").attr("style", "visibility: visible")
$("#bems-container").attr("style", "visibility: visible")
//self.response = ko.mapping.fromJS(ajax_response);
var foobar = {};
ko.mapping.fromJS(ajax_response, {}, foobar);
self.response(foobar);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With