How do I add custom data-bindings to knockoutjs' template binding - javascript

I am currently using the ExternalTemplate extension to have my templates loaded via ajax at runtime. However I am looking to extend this functionality slightly so I can supply more than one template directory.
I know it seems a bit bizarre, but I have a couple of places where templates could come from, and it is not possible to have them all coming out of one big template folder unfortunately.
I was hoping to do something like:
<script type="text/javascript">
var templateEngineSettings = {
templatesLocations: {
"default":"/view-templates-1"
"other1":"/view-templates-2"
"other2":"/somewhere-else/view-templates"
},
templateSuffix: ".template.html"
};
ko.externaljQueryTemplateEngine.setOptions(templateEngineSettings);
</script>
<div data-bind="template: {name: 'some-template', location:'default'}"></div>
<div data-bind="template: {name: 'some-other-template', location:'other1'}"></div>
<div data-bind="template: {name: 'some-new-template', location:'other3'}"></div>
However I cannot find any solid documentation on how to do this, so any help would be great!

The external template engine pulls its url base from:
ko.ExternalTemplateEngine.templateUrl
One choice would be to create a wrapper to the template binding that would swap this value from your template locations. Something like:
//custom binding
ko.bindingHandlers.templatex = {
update: function(element, valueAccessor, allBindingsAccessor, viewModel) {
var options = valueAccessor(),
location = options.location,
current = koExternalTemplateEngine.templateUrl;
//set to our new location
ko.ExternalTemplateEngine.templateUrl = ko.bindingHandlers.templatex.templateLocations[location];
//call the real template binding
ko.bindingHandlers.update.tempate(element, valueAccessor, allBindingsAccessor, viewModel);
//reset the location back to the default
ko.ExternalTemplateEngine.templateUrl = current;
},
templateLocations: {}
};
//set in your app code
ko.bindingHandlers.templatex.templateLocations = {
"default":"/view-templates-1",
"other1":"/view-templates-2",
"other2":"/somewhere-else/view-templates"
};

Related

Knockout: Best Way To Create Links In Rendered Content Bound To ViewModel Functions

I'm using Knockout to bind an element:
<div data-bind="foreach: someCollection">
<p data-bind="html: someTextProperty"></p>
</div>
However, there's text in someTextProperty that I want to turn into hyperlinks that interact with the ViewModel and one of its functions.
Is there an easy, supported way to perform bindings on dynamically rendered content after it's rendered? Is an afterRender binding in foreach (which requires a lot of logic to make sure I'm targeting the right element) the only thing available?
EDIT:
In my real-world scenario, someTextProperty would look something like this:
This is a paragraph with some <tag data-val="foo">tagged</tag> text.
...and I'm currently converting it to something like this:
This is a paragraph with some <a onclick="viewModel.DoSomething(\'foo\')">tagged</a> text.
...but referencing the view model function directly in the link feels a bit dirty, so I'm looking for a better way.
Ok, what it takes is a custom binding handler that wraps the html binding and applies bindings to the new code. I've just made a magical computed to transform the original data into the desired html, and bound the computed.
ko.bindingHandlers.boundHtml = {
update: function(element, valueAccessor, allBindingsAccessor, data, context) {
ko.bindingHandlers.html.update(element, valueAccessor, allBindingsAccessor, data, context);
ko.utils.arrayForEach(element.children, function(node) {
console.debug("Bind", node);
ko.applyBindingsToNode(node, context);
});
}
};
data = 'This is a paragraph with some <tag data-val="foo">tagged</tag> text.';
function myTransform() {
return 'This is a paragraph with some <a data-bind="click: DoSomething.bind(null, \'foo\')">tagged</a> text.';
}
vm = {
someTextProperty: ko.observable(data),
transformed: ko.computed({
deferEvaluation: true,
read: myTransform
}),
DoSomething: function(arg) {
console.debug("Doing something with", arg);
}
};
ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div data-bind="boundHtml: transformed"></div>

Jquery knockout dynamic template not working

I have a view model:
function ViewModel() {
this.applications = ko.observableArray();
this.templateView = ko.observable("application-grid");
this.templateToUse = function () {
return this.templateView();
}.bind(this);
};
var viewModel = new ViewModel();
ko.applyBindings(viewModel);
I have a list that is binded to the viewModel
<ul data-bind="template: { name: templateToUse, foreach: applications }"></ul>
When the page loads, it firs selects the "application-grid" template id.
When i change it first time, viewModel.templateView('application-list');, the template changes.
Then, if i change it back, viewModel.templateView('application-grid');, the template doesn't change anymore.
I am doing something wrong?
If you want to use knockout templates then you can specify that in bindings:
<ul data-bind="template: { name: templateToUse, foreach: applications, templateEngine: new ko.nativeTemplateEngine() }"></ul>
by default knockout will use jquery templates if it is referenced on page. You could also use other templates, for more info see documentation.

knockout css binding and the revealing module javascript patten

I am using the reveal module pattern for my javascript and I am having the darndest time getting the css binding to work correctly with knockout.
My JS
my.js.module = (function ($) {
"use strict";
var my = {
testUrl: null
},
testModel= {
stuff: [{
"testOne": null
}]
},
testViewModel = null,
testId: null;
my.bindStuff = function () {
testViewModel = ko.mapping.fromJS(testModel);
ko.applyBindings(testViewModel, $(my.testId).get(0));
$.getJSON(my.testUrl,
{},
function (data) {
var testModelData = {
stuff: data
};
ko.mapping.fromJS(testModelData, testViewModel);
});
};
return my;
}(jQuery));
and in my cshtml I have
<tbody data-bind="foreach: stuff">
<tr>
<td data-bind="text: testOne"></td>
</tr>
</tbody>
Now I want to use the css binding via knock out to get a css value based on the value of what testOne is, it can be one of three things. I know it will be a ko,computed function but i cant quite figure out how to get each particular instance of stuff to look at testOne and get the correct value to determine what to return via the ko.computed function.
if someone could help me out i would be greatly appreciative.
This fiddle shows how to use mapping with an array and set the class of an element via knockout, its not an exact match to your code but should help:
http://jsfiddle.net/davidoleary/UHaVV/
var newData = {
test:"this is a test",
stuff:[
{"testOne":"Event1"},
{"testOne":"Event2"},
{"testOne":"Event3"}
]
};
var viewModel = ko.mapping.fromJS( newData );
ko.applyBindings(viewModel);
<div data-bind="text:test"></div>
<ul data-bind="foreach: stuff">
<li><span data-bind="text: testOne, attr:{class: testOne}"></span></li>
</ul>
Also have a look at this question to see two ways of setting the class:
Knockout binding css class to an observed model property
You can use attr or the new css binding both are refereed to in the question above.

Creating Bootstrap tabs using Knockout.js foreach

I'm trying to create some tabs, one per profile the user chooses to save. Each profile is a ViewModel. So I thought I'd just create another ViewModel that contains an observableArray of objects of type: {name: profile_name, model: model_converted_to_json}.
I followed this example to create my code - but I get nothing bound, for some reason.
Here's my code:
-ViewModel (I use Requirejs, that explains the external wrapper):
"use strict";
// profiles viewmodel class
define(["knockout"], function(ko) {
return function() {
var self = this;
this.profilesArray = ko.observableArray();
this.selected = ko.observable();
this.addProfile = function(profile) {
var found = -1;
for(var i = 0; i < self.profilesArray().length; i++) {
if(self.profilesArray()[i].name == profile.name) {
self.profilesArray()[i].model = profile.model;
found = i;
}
}
if(found == -1) {
self.profilesArray().push(profile);
}
};
};
});
-The JS code (excerpt of larger file):
var profiles = new profilesViewMode();
ko.applyBindings(profiles, $("#profileTabs")[0]);
$("#keepProfile").on("click", function() {
var profile = {
name: $("#firstName").text(),
model: ko.toJSON(model)
};
profiles.addProfile(profile);
profiles.selected(profile);
console.log(profile);
$("#profileTabs").show();
});
-The HTML (Thanks Sara for correcting my HTML markup)
<section id="profileTabs">
<div>
<ul class="nav nav-tabs" data-bind="foreach: profilesArray">
<li data-bind="css: { active: $root.selected() === $data }">
</li>
</ul>
</div>
</section>
I have verified that the observableArray does get new, correct value on button click - it just doesn't get rendered. I hope it's a small thing that I'm missing in my Knockout data-bind syntax.
Thanks for your time!
You will want to call push directly on the observableArray, which will both push to the underlying array and notify any subscribers. So:
self.profilesArray.push(profile);
You are setting name using name: $('#firstName').text(); you may need to change that to .val() if this is referencing an input field (which I assumed here).
You are using .push() on the underlying array which bypasses ko's subscribers (the binding in this case)
Here is a working jsfiddle based on your code. I took some liberties with model since that wasn't included.

jquery templating - import a file?

I'm working with backbone.js, but as far as I've seen, it doesn't care what templating system you use. Currently I'm trying out mustache.js, but I'm open to other ones. I'm a little annoyed though with the way I have to put a template into a string:
var context = {
name: this.model.get('name'),
email: this.model.get('email')
}
var template = "<form>Name<input name='name' type='text' value='{{name}}' />Email<input name='email' type='text' value='{{email}}' /></form>";
var html = Mustache.to_html(template, context);
$(this.el).html(html);
$('#app').html(this.el);
I'd like if I could load it from a different file or something somehow. I want to be able to have template files in order to simplify things. For example, if I put it all in a string, I can't have breaks (well I can have html breaks, but that's not the point). After the line starts to get very long, it becomes unmanageable.
Tips?
Updated (4/11/14):
As answered by OP below:
Unfortunately, the jQuery team has moved the templating functionality out of jQuery Core. The code is still available as a library here: github.com/BorisMoore/jquery-tmpl and here: github.com/borismoore/jsrender
Original Answer:
I just used this a couple of hours ago:
http://api.jquery.com/category/plugins/templates/
It's an official jQuery plugin(i.e. the devs endorse it).
This is the function you need to use for loading templates from things other than strings: http://api.jquery.com/template/
Here's the code to have a template in HTML:
<script id="titleTemplate" type="text/x-jquery-tmpl">
<li>${Name}</li>
</script>
___________
// Compile the inline template as a named template
$( "#titleTemplate" ).template( "summaryTemplate" );
function renderList() {
// Render the movies data using the named template: "summaryTemplate"
$.tmpl( "summaryTemplate", movies ).appendTo( "#moviesList" );
}
It's in a <script> tag, because that's not visible by default.
Note the type="text/x-jquery-tmpl". If you omit that, it will try to parse it as JavaScript(and fail horribly).
Also note that "loading from a different file" is essentially the same as "reading a file" and then "loading from a string".
Edit
I just found this jQuery plugin - http://markdalgleish.com/projects/tmpload/ Does exactly what you want, and can be coupled with $.tmpl
I have built a lightweight template manager that loads templates via Ajax, which allows you to separate the templates into more manageable modules. It also performs simple, in-memory caching to prevent unnecessary HTTP requests. (I have used jQuery.ajax here for brevity)
var TEMPLATES = {};
var Template = {
load: function(url, fn) {
if(!TEMPLATES.hasOwnProperty(url)) {
$.ajax({
url: url,
success: function(data) {
TEMPLATES[url] = data;
fn(data);
}
});
} else {
fn(TEMPLATES[url]);
}
},
render: function(tmpl, context) {
// Apply context to template string here
// using library such as underscore.js or mustache.js
}
};
You would then use this code as follows, handling the template data via callback:
Template.load('/path/to/template/file', function(tmpl) {
var output = Template.render(tmpl, { 'myVar': 'some value' });
});
We are using jqote2 with backbone because it's faster than jQuery's, as you say there are many :)
We have all our templates in a single tpl file, we bind to our template_rendered so we can add jquery events etc etc
App.Helpers.Templates = function() {
var loaded = false;
var templates = {};
function embed(template_id, parameters) {
return $.jqote(templates[template_id], parameters);
}
function render(template_id, element, parameters) {
var render_template = function(e) {
var r = embed(template_id, parameters);
$(element).html(r);
$(element).trigger("template_rendered");
$(document).unbind(e);
};
if (loaded) {
render_template();
} else {
$(document).bind("templates_ready", render_template);
}
}
function load_templates() {
$.get('/javascripts/backbone/views/templates/templates.tpl', function(doc) {
var tpls = $(doc).filter('script');
tpls.each(function() {
templates[this.id] = $.jqotec(this);
});
loaded = true;
$(document).trigger("templates_ready");
});
}
load_templates();
return {
render: render,
embed: embed
};
}();
They look like
<script id="table" data-comment="generated!!!!! to change please change table.tpl">
<![CDATA[
]]>
</script>

Categories

Resources