Reputation: 341
I am using the KO external template engine to break down my single page webapp into multiple files, but the templates I am loading only contain KO markup.
Despite researching the topic extensively I can't figure out how to reliably apply KO bindings to the templates that are being loaded.
The key points are that:
This is is the code I have, which has at least two issues:
Any suggestion for a clean, reliable method of applying bindings once and only once to external template nodes as they are being added to the DOM?
In index.html:
<div id="templateDiv" data-bind="template: { name: currentView() }"></div>
In main.js:
function AdminViewModel() {
var self = this;
self.currentView = ko.observable('adminHome');
}
var viewModel = new AdminViewModel();
var SammyApp = $.sammy('#admin_content', function() {
//...
this.get('#/editMembers', function(context) {
viewModel.currentView('editMembers');
ko.applyBindings(viewModel, $('.ko-template').get(0));
});
};
ko.applyBindings(viewModel);
Upvotes: 1
Views: 1122
Reputation: 341
I came up with something that works using an afterRender callback... although a bit messy IMHO, improvements welcome.
The afterRender callback is invoked twice for some reason, the second time around with an empty object, hence the test for hasOwnProperty('nodeType').
isBound() checks if the bindings have already been applied to the element - an attempt to add a custom marker CSS ('ko-applied') class to the element after the applying the bindings didn't work reliably.
Not sure that copying the koElements array is actually needed, but without that I have ended up with undefined elements[i] in the loop, so the template loader may be asynchronously updating the array while afterRender is running.
<div id="templateDiv" data-bind="template: { name: currentView(), afterRender: applyTemplateBindings }"></div>
self.applyTemplateBindings = function(koElements) {
var elements = koElements.slice();
for (var i = 0, len = elements.length; i < len; i++) {
var element = elements[i];
if (element.hasOwnProperty('nodeType') && ! $(element).hasClass('infuser-loading') &&
! isBound(element)) {
ko.applyBindings(self, element);
}
}
};
var isBound = function(node) {
return !!ko.dataFor(node);
};
Upvotes: 0
Reputation: 17554
I have made a Template engine for KO
https://github.com/AndersMalmgren/Knockout.Bootstrap.TemplateStore/wiki
It requires a Owin enabled web server, once configured it understands that a ViewModel named FooViewModel
should be connected to the View called FooView
Install using nuget (For ASP.NET)
Install-Package Knockout.Bootstrap.TemplateStore.SystemWeb
It is also designed to be easy to use in a SPA
Demo https://github.com/AndersMalmgren/Knockout.Bootstrap.Demo
Upvotes: 0
Reputation: 8053
This is not a solution on how to use the templates, but this is how I split into files using the with binding.
Suppose you have a page like this:
<div data-bind="with: block1">
<input data-bind="value: yourFirstInput" />
<!-- more markeup -->
</div>
<div data-bind="with: block2">
<select data-bind="options: dropDownlist2options"></select>
<!-- more markeup -->
</div>
You can put the block1
in a file (I use ascx
), block2
in another file.
Then in your viewmodel you have something like:
var viewmodel = function () {
var self = this;
this.block1 = ko.observable();
this.block2 = ko.observable();
}
var vm = new viewmodel();
ko.applyBindings(vm);
This way you applyBindings on the whole page. The with binding
will handle the check for null/undefined objects and display the blocks only when the obects are instanciated.
You can then do something like this when you want to display block1
:
vm.block1({ yourFirstInput: ko.observable('aa')});
Actually I do that with the mapping plugin, this is just an example.
Live demo (notice how the block1 appears after the setTimeout)
Upvotes: 0
Reputation: 863
if i got you point then you can do this
load you template in one base file in js using get and assign to script tag as below
var script = document.createElement("script");
script.id = "YourTemplateName";
script.type = "text/html";
script.text = result.Value; //template data
document.body.appendChild(script);
and in your index/base html file assign template
<div id="OtherTemplateDiv" data-bind="template: { name: 'YourTemplateName' }">
</div>
Upvotes: 1