Async loading a template in a Knockout component - javascript

I'm pretty experienced with Knockout but this is my first time using components so I'm really hoping I'm missing something obvious! I'll try and simplify my use case a little to explain my issue.
I have a HTML and JS file called Index. Index.html has the data-bind for the component and Index.js has the ko.components.register call.
Index.html
<div data-bind="component: { name: CurrentComponent }"></div>
Index.js
var vm = require("SectionViewModel");
var CurrentComponent = ko.observable("section");
ko.components.register("section", {
viewModel: vm.SectionViewModel,
template: "<h3>Loading...</h3>"
});
ko.applyBindings();
I then have another HTML and JS file - Section.html and SectionViewModel.js. As you can see above, SectionViewModel is what I specify as the view model for the component.
Section.html
<div>
<span data-bind="text: Section().Name"></span>
</div>
SectionViewModel.js
var SectionViewModel = (function() {
function SectionViewModel() {
this.Section = ko.observable();
$.get("http://apiurl").done(function (data) {
this.Section(new SectionModel(data.Model)); // my data used by the view model
ko.components.get("dashboard", function() {
component.template[0] = data.View; // my html from the api
});
});
}
return SectionViewModel;
});
exports.SectionViewModel = SectionViewModel;
As part of the constructor in SectionViewModel, I make a call to my API to get all the data needed to populate my view model. This API call also returns the HTML I need to use in my template (which is basically being read from Section.html).
Obviously this constructor isn't called until I've called applyBindings, so when I get into the success handler for my API call, the template on my component is already set to my default text.
What I need to know is, is it possible for me to update this template? I've tried the following in my success handler as shown above:
ko.components.get("section", function(component) {
component.template[0] = dataFromApi.Html;
});
This does indeed replace my default text with the html returned from my API (as seen in debug tools), but this update isn't reflected in the browser.
So, basically after all that, all I'm really asking is, is there a way to update the content of your components template after binding?
I know an option to solve the above you might think of is to require the template, but I've really simplified the above and in it's full implementation, I'm not able to do this, hence why the HTML is returned by the API.
Any help greatly appreciated! I do have a working solution currently, but I really don't like the way I've had to structure the JS code to get it working so a solution to the above would be the ideal.
Thanks.

You can use a template binding inside your componente.
The normal use of the template bindign is like this:
<div data-bind="template: { name: tmplName, data: tmplData }"></div>
You can make both tmplData and tmplName observables, so you can update the bound data, and change the template. The tmplName is the id of an element whose content will be used as template. If you use this syntax you need an element with the required id, so, in your succes handler you can use something like jQuery to create a new element with the appropriate id, and then update the tmplname, so that the template content gets updated.
*THIS WILL NOT WORK:
Another option is to use the template binding in a different way:
<div data-bind="template: { nodes: tmplNodes, data: tmplData }"></div>
In this case you can supply directly the nodes to the template. I.e. make a tmplNodes observable, which is initialized with your <h3>Loading...</h3> element. And then change it to hold the nodes received from the server.
because nodesdoesn't support observables:
nodes — directly pass an array of DOM nodes to use as a template. This should be a non-observable array and note that the elements will be removed from their current parent if they have one. This option is ignored if you have also passed a nonempty value for name.
So you need to use the first option: create a new element, add it to the document DOM with a known id, and use that id as the template name. DEMO:
// Simulate service that return HTML
var dynTemplNumber = 0;
var getHtml = function() {
var deferred = $.Deferred();
var html =
'<div class="c"> \
<h3>Dynamic template ' + dynTemplNumber++ + '</h3> \
Name: <span data-bind="text: name"/> \
</div>';
setTimeout(deferred.resolve, 2000, html);
return deferred.promise();
};
var Vm = function() {
self = this;
self.tmplIdx = 0;
self.tmplName = ko.observable('tmplA');
self.tmplData = ko.observable({ name: 'Helmut', surname: 'Kaufmann'});
self.tmplNames = ko.observableArray(['tmplA','tmplB']);
self.loading = ko.observable(false);
self.createNewTemplate = function() {
// simulate AJAX call to service
self.loading(true);
getHtml().then(function(html) {
var tmplName = 'tmpl' + tmplIdx++;
var $new = $('<div>');
$new.attr('id',tmplName);
$new.html(html);
$('#tmplContainer').append($new);
self.tmplNames.push(tmplName);
self.loading(false);
self.tmplName(tmplName);
});
};
return self;
};
ko.applyBindings(Vm(), byName);
div.container { border: solid 1px black; margin: 20px 0;}
div {padding: 5px; }
.a { background-color: #FEE;}
.b { background-color: #EFE;}
.c { background-color: #EEF;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="byName" class="container">
Select template by name:
<select data-bind="{options: tmplNames, value: tmplName}"></select>
<input type="button" value="Add template"
data-bind="click: createNewTemplate"/>
<span data-bind="visible: loading">Loading new template...</span>
<div data-bind="template: {name: tmplName, data: tmplData}"></div>
</div>
<div id="tmplContainer" style="display:none">
<div id="tmplA">
<div class="a">
<h3>Template A</h3>
<span data-bind="text: name"></span> <span data-bind="text: surname"></span>
</div>
</div>
<div id="tmplB">
<div class="b">
<h3>Template B</h3>
Name: <span data-bind="text: name"/>
</div>
</div>
</div>

component.template[0] = $(data)[0]
I know this is old, but I found it trying to do the same, and the approcah helped me come up with this in my case, the template seems to be an element, not just raw html

Related

KnockoutJS Observable being rebound appropriately, but calling gives an empty string?

So I've got a section that updates based on a ko.observable called toClicked, see below:
<div id="longInfoWindow">
<div id="longInfo_goBack"><span class="fa fa-arrow-left"></span> Go Back</div>
<div id="location_mainInfo">
<h1 id="location_title" data-bind="text: toClicked.title">Title</h1>
<h2 id="location_address" data-bind="text: toClicked.address">Address</h2>
<h6 class="location_latlng">LAT: <span data-bind="text: toClicked.lat">LATITUDE</span></h6>
<h6 class="location_latlng">LNG: <span data-bind="text: toClicked.lng">LONGITUDE</span></h6>
<p id="location_description" data-bind="text: toClicked.content">
Empty
</p>
</div>
</div>
toClicked is data-bound via a for-each ul that passes in bits of information from an observableArray, so this data is constantly changing - here's what the click function looks like in the viewmodel.
var viewModel = {
// Nav open and close via knockoutjs
self : this,
userQuery : ko.observable(''),
toClicked : ko.observable({}),
logClick : function(clicked){
var toClicked = ko.observable({});
toClicked().title = clicked.title;
toClicked().lat = clicked.lat;
toClicked().lng = clicked.lng;
toClicked().placeID = clicked.placeID;
toClicked().address = clicked.address;
toClicked().content = clicked.content;
return toClicked();
}
};
// at the end of the document...
ko.applyBindings(viewModel);
For some reason, I can call any toClicked parameter, like toClicked.title, and I get the proper output. But I can't get anything to bind in the longInfoWindow bit of code, it removes the filler text with empty strings. Is there something I'm missing here with Knockout that's keeping it from updating appropriately?
As a side note, I've tried setting the databinds to viewModel.toClicked.title with no joy. Have also tried $root, $parent. either comes back as not defined or gives the same result.
You need to change the way toClicked is accessed. Given that it is an observable, it must access the properties using syntax like toClicked().property. Another problem is that you should specify toClicked as an object, before setting properties on it.
Also, since clicked is an array, it should be accessed by index, like clicked[0].title.
Note the use of self.toClicked.valueHasMutated(); in function logClick. It tells the view model that observable variable toClicked has some non-observable properties that might have changed. As a result the view model is updated. You should avoid using it excessively.
var viewModel = function() {
// Nav open and close via knockoutjs
var self = this;
self.test = ko.observable('text');
self.userQuery = ko.observable('');
self.toClicked = ko.observable({});
self.markers = ko.observableArray([
{ title: 'Eagle River Airport', lat: 45.934099, lng: -89.261834, placeID: 'ChIJdSZITVA2VE0RDqqRxn-Kjgw', content: 'This is the Eagle River Airport. Visit us for fly-ins!' }
]);
self.logClick = function(clicked) {
// var toClicked = ko.observable({});
self.toClicked().title = clicked[0].title;
self.toClicked().lat = clicked[0].lat;
self.toClicked().lng = clicked[0].lng;
self.toClicked().placeID = clicked[0].placeID;
self.toClicked().address = clicked[0].address;
self.toClicked().content = clicked[0].content;
self.toClicked.valueHasMutated();
return self.toClicked();
};
};
// at the end of the document...
var vm = new viewModel();
ko.applyBindings(vm);
var markers = vm.markers();
vm.logClick(markers);
Your view model must also change slightly. Note the () brackets after toClicked, they are used to access the properties of an observable.
<div id="longInfoWindow">
<div id="longInfo_goBack"><span class="fa fa-arrow-left"></span> Go Back</div>
<div id="location_mainInfo">
<h1 id="location_title" data-bind="text: toClicked().title">Title</h1>
<h2 id="location_address" data-bind="text: toClicked().address">Address</h2>
<h6 class="location_latlng">LAT: <span data-bind="text: toClicked().lat">LATITUDE</span></h6>
<h6 class="location_latlng">LNG: <span data-bind="text: toClicked().lng">LONGITUDE</span></h6>
<p id="location_description" data-bind="text: toClicked().content">
Empty
</p>
</div>
I'd also suggest that instead of accessing toClicked directly within logClick you use something like self.toClicked to avoid ambiguity. See this.
Here's the working fiddle: https://jsfiddle.net/Nisarg0/hx0q6tt6/13/
The more obvious way without having to use valueHasMutated would be to assign directly to the observable:
self.logClick = function(clicked) {
self.toClicked({
lat: clicked[0].lat,
lng: clicked[0].lng,
placeID: clicked[0].placeID,
adress: clicked[0].address,
content: clicked[0].content
});
};
You normally do not need to use valueHasMutated when using knockout. Also there is no need to return the observable from the click handler. In your bindings you need then to access the properties as already stated like this:
<h1 id="location_title" data-bind="text: toClicked().title">Title</h1>
Knockout will automatically update the heading, whenever toClicked changed.

knockout js cannot delete after adding to observable array

I am trying to complete some basic add and delete functionality using knockout js.
I am using asp mvc, the knockout mappings plugin and returning a simple list of strings as part of my viewModel
Currently I get three items from the server and the functionality I've created allows me to delete each of those items. I can also add an item. But if I try to delete an item i have added in the KO script I cannot delete it.
After completing research and some tests I guess i'm using the observable's incorrectly. I altered my code to pass ko.observable(""), but that has not worked. What am I doing wrong?
values on load
Array[4]0: "test 1"1: "test 2"2: "test 3"length: 4__proto__: Array[0]
values after clicking add
Array[4]0: "test 1"1: "test 2"2: "test 3"3: c()length: 4__proto__: Array[0]
ko script
var vm = function (data) {
var self = this;
ko.mapping.fromJS(data, {}, self);
this.deleteBulletPoint = function (bulletPoint) {
self.BulletPoints.remove(bulletPoint)
}
this.addEmptyBulletPoint = function () {
self.BulletPoints.push(ko.observable(""));
console.log(self.BulletPoints())
}
}
HTML
<div class="col-lg-6 col-md-6 col-sm-12">
<h4>Bullet Points</h4>
<div id="oneLinedescriptions" class="input_fields_wrap">
<!-- ko foreach: BulletPoints -->
<div class="form-group">
<div class="input-group">
<input type="text" data-bind="value: $data" class="form-control">
<span data-bind="click: $parent.deleteBulletPoint" class="input-group-addon btn">-</span>
</div>
</div>
<!-- /ko -->
</div>
<button id="btnAddDescription" data-bind="click: addEmptyBulletPoint" type="button" class="btn btn-default add_field_button col-lg-12 animate-off">Add New Bullet Point</button>
</div>
EDIT
I have removed $parent but the below error was returned
Uncaught ReferenceError: Unable to process binding "foreach: function (){return BulletPoints }"
Message: Unable to process binding "click: function (){return deleteBulletPoint }"
Message: deleteBulletPoint is not defined
In addition to this I have been able to add new empty elements but they are not updated when a user changes the value. is this because the element I am adding is not observable? And if so how do I get round it?
I've added a snippet to ilustrate how you add and remove using an Observable Array with Knockout, the majority of the code is straight from yours so you are in the right track.
Note that when binding to the methods, in this case, there's no need to reference the $parent as you are not using nested scopes. Also, when adding, you just need to pass in plain text, as the observableArray is expecting an object.
If you work with more complex types, when adding you'll need to pass the object itself, and reference its properties from it inside the scope of the iterator, you can read more about it here.
Hope this helps.
var vm = function (data) {
var self = this;
this.BulletPoints = ko.observableArray(["test1", "test2", "test3"]);
this.deleteBulletPoint = function (bulletPoint) {
self.BulletPoints.remove(bulletPoint)
}
this.addEmptyBulletPoint = function () {
const c = self.BulletPoints().length + 1;
self.BulletPoints.push("test "+c);
}
}
ko.applyBindings(vm);
a {
cursor: pointer;
color: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h4>Bullet Points <span><a data-bind="click: addEmptyBulletPoint">Add +</a></span></h4>
<div data-bind="foreach: BulletPoints">
<div class="input-group">
<h3 data-bind="text: $data"></h3>
<a data-bind="click: deleteBulletPoint">Delete</a>
</div>
<div>
The problems you're having are related to the way ko.mapping treats arrays of primitive types. By design only properties that are part of an object get mapped to observables so an array of strings will become an observableArray of strings. It will not become an observableArray of observable strings.
In order to add/remove items from an array where the items themselves are observable you'll have to make your BulletPoints array an array of objects having the string as a property within:
data = { BulletPoints: [{value: "test1"}, {value: "test2"}] }
Here's a working example: jsfiddle

Data binding while manipulating the DOM not working with Angular, Knockout

I am looking to implement Angular, Knockout or another to data-bind a wizard-style application form proof-of-concept (no server-side code). However I appear to be breaking the data bindings when I clone the data-bound div.
The first few steps of the application form capture data, while the later steps present the data back to the user to allow them confirm what was entered. I am manipulating the DOM by inserting the appropriate step when 'next' is pressed and taking out the last step when 'previous' is pressed. I do this using detatch, clone and remove.
Can anyone give advise on the approach they would take to make this work, and what frameworks I should look at?
Below is pseudocode to give an idea of the structure. The pseudo-data-binding-code is just how I thought it would work, I'm not bedded to any framework.
HTML View
<div id="wizard">
<div id="step1">Enter your name: <input type="text" id="name" /></div>
</div>
<div id="actions"><input type="button" value="Previous" /><input type="button" value="Next" onClick="goNext();" /></div>
<div id="steps">
<div id="stepA">Enter your age: <input type="text" id="age" databind="theAge" /></div>
<div id="stepB">The age you entered - {{ theAge }} is too young!</div>
<div id="stepC">Enter your favourite colour: <input type="text" id="faveColour" databind="faveCol" /></div>
<div id="stepD">Hi {{ name }}. You are {{ age }} years old and your favourite colour is {{ faveCol }}</div>
</div>
JavaScript
<script>
function goNext() {
// figure out which step is next
insertStepIntoWizard(step, index, title);
}
function insertStepIntoWizard(step, index, title) {
var element = step.detach();
wizard.steps('insert', index, {
title: title,
content: element.clone()
});
console.log('insertStepIntoWizard - just inserted ' + step.attr('id') + ' into wizard position ' + index);
}
</script>
I think you're thinking in terms of having your view reflect your entity model, which is basically templating. If you're looking to use Knockout/Angular, consider using it's view model to manage page state / flow / actions, in addition to controlling the entity. (Writing jQuery code to poke about with the DOM and clone, show/hide, etc is no fun). #sabithpocker makes a similar point.
Working example: I'm familiar with Knockout, and have created an example jsFiddle of your scenario: http://jsfiddle.net/overflew/BfRq8/5/
Notes:
I've used the template tag to house each section of the wizard, and all steps point to the same model/entity within the viewmodel.
To emphasise what's happening with the publish/subscribe nature of the bindings:
The user input is also relayed at the bottom of the page.
The form title is dynamic, as well as the 'step'
ko.computed is used to 'compute' the full name and if there are any 'steps' left to go
Knockout-specific: Notice the occurrence of brackets popping up around the place. This is one thing that may catch you out occasionally if you choose to learn Knockout. It just means that you're evaluating the binding container to get the value.
View model
<div>
<h3 data-bind="text: currentStep().name"></h3>
<div data-bind="template: { name: 'wizard-step1' }, visible: currentStep().id === 0"></div>
<div data-bind="template: { name: 'wizard-step2' }, visible: currentStep().id === 1"></div>
<div data-bind="template: { name: 'wizard-step3' }, visible: currentStep().id === 2"></div>
<input type="button" value="Next step" data-bind="click: onNext, visible: hasNextSteps" />
<input type="button" value="Submit" data-bind="click: onSubmit,visible: !hasNextSteps()" />
<div data-bind="visible: submitResultMessage, text: submitResultMessage"></div>
</div>
<div>
<h3>Your inputs</h3>
<div data-bind="visible: questions.fullName">Full name: <span data-bind="text: questions.fullName"></span></div>
<div data-bind="visible: questions.age">Age: <span data-bind="text: questions.age"></span>
</div>
<div data-bind="visible: questions.favouriteColour">Favourite colour: <span data-bind="text: questions.favouriteColour"></span>
</div>
</div>
<script type="text/html" id="wizard-step1">
<div>
First name: <input data-bind="value: questions.firstName, valueUpdate:'afterkeydown'" />
</div>
<div>
Last name: <input data-bind="value: questions.lastName, valueUpdate:'afterkeydown'" />
</div>
</script>
<script type="text/html" id="wizard-step2">
<div>
Age: <input data-bind="value: questions.age, valueUpdate:'afterkeydown'" />
</div>
</script>
<script type="text/html" id="wizard-step3">
<div>
Favourite colour: <input data-bind="value: questions.favouriteColour, valueUpdate:'afterkeydown'" />
</div>
</script>
View
// Entity for holding form data.
var FormData = function() {
var self = this;
self.firstName = ko.observable("");
self.lastName = ko.observable("");
self.age = ko.observable("");
self.favouriteColour = ko.observable("");
self.fullName = ko.computed(function() {
if (!self.firstName() && !self.lastName()) {
return "";
}
return self.firstName() + " " + self.lastName();
});
}
// Quick handling for managing steps of the wizard
var wizardSteps = [
{ id: 0, name: "Wizard step 1"},
{ id: 1, name: "More questions"},
{ id: 2, name: "Last step"}
];
var ViewModel = function() {
var self = this;
// Properties
self.questions = new FormData();
self.currentStep = ko.observable(wizardSteps[0]);
self.submitResultMessage = ko.observable();
// Actions
self.onNext = function() {
var currentIndex = self.currentStep().id;
if (self.hasNextSteps()) {
// Move forward one step on the wizard
self.currentStep(wizardSteps[currentIndex + 1]);
}
};
self.onSubmit = function() {
self.submitResultMessage("Data is now submitted ");
};
// Page control
self.hasNextSteps = ko.computed(function() {
var currentIndex = self.currentStep().id;
return currentIndex < wizardSteps.length - 1;
});
};
ko.applyBindings(new ViewModel());
What most of the bleeding edge javascript frameworks is trying to do is attempting to manage the huge amount of code in current applications where large amount of business logic is implemented with javascript in client side. So they are mostly trying to provide you with some architecture to organize your code to make it easy to manage, read and scale.
In your case you are trying to neglect the architecture like MVC or MVWhatever they are providing and use the framework to do some templating, If I understand you correctly. For that you can better get the help of some templating engine in javascript like handlebars and use it to manually render your data stored in your current javascript app.
see it here http://handlebarsjs.com/
Simply re-initialize your bindings.
This solves my problem when using knockout.
When you change the main DOM element which is actually being referenced for MVVM, it turn-out that any change or re-structuring that element hampers the dom relationship.
Eventually, though the placement of the item looks same but technically, in java script engine it is not. So the problem occurs.
In order to fix something like this, simply have a constructor which will initialize or re-initialize the mapping for a particular element in DOM.
Hope that solves your problem.
Example:
function ReInitializeVM()
{
ko.cleanNode(document.getElementById("userDetails"));
ko.applyBindings(viewModel, document.getElementById("userDetails"));
}
The function can be called everytime where you feel the need of re-initializing the bindings. Calling the function will remove any binding on the element with id "userDetails" and then the applyBindings can be called to initialize the binding. This will invoke bindings for any new/changed element.

Passing values to ko.computed in Knockout JS

I've been working for a bit with MVC4 SPA, with knockoutJs,
My problem is I want to pass a value to a ko.computed. Here is my code.
<div data-bind="foreach: firms">
<fieldset>
<legend><span data-bind="text: Name"></span></legend>
<div data-bind="foreach: $parent.getClients">
<p>
<span data-bind="text: Name"></span>
</p>
</div>
</fieldset>
</div>
self.getClients = ko.computed(function (Id) {
var filter = Id;
return ko.utils.arrayFilter(self.Clients(), function (item) {
var fId = item.FirmId();
return (fId === filter);
});
});
I simply want to display the Firmname as a header, then show the clients below it.
The function is being called, but Id is undefined (I've tried with 'Firm' as well), if I change:
var filter = id; TO var filter = 1;
It works fine,
So... How do you pass a value to a ko.computed? It doesn't need to be the Id, it can also be the Firm object etc.
Each firm really should be containing a list of clients, but you could use a regular function I think and pass it the firm:
self.getClientsForFirm = function (firm) {
return ko.utils.arrayFilter(self.Clients(), function (item) {
var fId = item.FirmId();
return (fId === firm.Id());
});
});
Then in html, $data is the current model, in your case the firm:
<div data-bind="foreach: $root.getClientsForFirm($data)">
Knockout doesn't allow you pass anything to a computed function. That is not what it is for. You could instead just use a regular function there if you'd like.
Another option is to have the data already in the dataset on which you did the first foreach. This way, you don't use $parent.getClients, but more like $data.clients.

How to bind click handlers to templates in knockoutjs without having a global viewModel?

I'm very new to KnockoutJs so I'm hoping that there is a well known best practice for this kind of situation that I just haven't been able to find.
I have a view model that contains an array of items. I want to display these items using a template. I also want each item to to be able to toggle between view and edit modes in place. I think what fits best with Knockout is to create the relevant function on either the main view model or (probably better) on each item in the array and then bind this function in the template. So I have created this code on my page:
<ul data-bind="template: {name: testTemplate, foreach: items}"></ul>
<script id="testTemplate" type="text/x-jquery-tmpl">
<li>
<img src="icon.png" data-bind="click: displayEditView" />
<span data-bind="text: GBPAmount"></span>
<input type="text" data-bind="value: GBPAmount" />
</li>
</script>
<script>
(function() {
var viewModel = new TestViewModel(myItems);
ko.applyBindings(viewModel);
})();
</script>
And this in a separate file:
function TestViewModel(itemsJson) {
this.items = ko.mapping.fromJS(itemsJson);
for(i = 0; i < this.items.length; ++i) {
this.items[i].displayEditView = function () {
alert("payment function called");
}
}
this.displayEditView = function () {
alert("viewmodel function called");
}
};
Due to the environment my JS is running in I can't add anything to the global namespace, hence the annonymous function to create and set up the view model. (There is a namespace that I can add things to if it is necessary.) This restriction seems to break all the examples I've found, which seem to rely on a global viewModel variable.
P.S. If there's an approach that fits better with knockoutJS than what I am trying to do please feel free to suggest it!
When your viewModel is not accessible globally, there are a couple of options.
First, you can pass any relevant methods using the templateOptions parameter to the template binding.
It would look like (also note that a static template name should be in quotes):
data-bind="template: {name: 'testTemplate', foreach: items, templateOptions: { vmMethod: methodFromMainViewModel } }"
Then, inside of the template vmMethod would be available as $item.vmMethod. If you are using templateOptions as the last parameter, then make sure that there is a space between your braces { { or jQuery templates tries to parse it as its own.
So, you can bind to it like:
<img src="icon.png" data-bind="click: $item.vmMethod" />
The other option is to put a method and a reference to anything relevant from the view model on each item. It looks like you were exploring that option.
Finally, in KO 1.3 (hopefully out in September and in beta soon) there will be a nice way to use something like jQuery's live/delegate functionality and connect it with your viewModel (like in this sample: http://jsfiddle.net/rniemeyer/5wAYY/)
Also, the "Avoiding anonymous functions in event bindings" section of this post might be helpful to you as well. If you are looking for a sample of editing in place using a dynamically chosen template, then this post might help.
This is for those asking how to pass variable methods (functions) to Knockout Template. One of the core features of Templating is the consuming of variable data, which can be String or function. In KO these variables can be embedded in data or foreach properties for the Template to render. Objects embedded in data or foreach, be it String, function etc, can be accessed at this context using $data.
You can look at this code and see if it can help you to pass functions to Knockout Template.
function ViewModel() {
this.employees = [
{ fullName: 'Franklin Obi', url: 'employee_Franklin_Obi', action: methodOne },
{ fullName: 'John Amadi', url: 'employee_John_Amadi', action: methodTwo }
],
this.methodOne = function(){ alert('I can see you'); },
this.methodTwo = function(){ alert('I can touch you'); }
}
ko.applyBindings(new ViewModel());
<ul data-bind="template: { name: employeeTemplate, foreach: employees }" ></ul>
<script type="text/html" id="employeeTemplate">
<li><a data-bind="attr: { href: '#/'+url }, text: fullName, click: $data.action"></a></li>
</script>
If you want to serve multiple Template constructs you can introduce a switch method to your ViewModel like this, and use as property to introduce alias for each item (employee). Make sure you add the switch key, linkable, to the item object.
...
this.employees = [
{ fullName: 'Franklin Obi', linkable : false },
{ fullName: 'John Amadi', url: 'employee_John_Amadi', action: methodTwo, linkable : true }
],
this.methodLinkTemplate = function(employee){return employee.linkable ? "link" : "noLink"; } //this is a two way switch, many way switch is applicable.
...
Then the id of the Template forms will be named thus;
<ul data-bind="template: { name: employeeTemplate, foreach: employees, as: 'employee' }" ></ul>
<script type="text/html" id="noLink">
<li data-bind="text: fullName"></li>
</script>
<script type="text/html" id="link">
<li><a data-bind="attr: { href: '#/'+url }, text: fullName, click: $data.action"></a></li>
</script>
I have not ran this codes but I believe the idea can save someones time.

Categories