I get the error Object [object Array] has no method 'search' in mustache.js line 103.
I've created an object of templates when the page is ready:
window.templates = {};
$("script[type='text/mustache']").each(function(){
window.templates[this.id] = Mustache.parse(this.innerHTML);
});
I then have a json object from my server, the relevant part of which I'm passing into a rendering function.
var render = function(data){
var content = "";
for (n in data){
content += Mustache.render(window.templates[n], data[n], window.templates);
}
return content;
}
I'm attempting to match up templates with models: the window.template keys matching the keys in my data object returned from my model. Since templates may contain other templates as partials, I'm passing the entire templates object back in at the end. I can confirm that I'm getting the model name I expect, and that it does properly match with a template.
I'm new to Mustache, so this is probably a simple fix. What am I missing?
Thanks
Without Mustache version I can only guess but it seems you are using version 0.7.3. If so then you are looking at wrong documentation ("Pre-parsing and Caching Templates" in master branch). And what you should be looking is "Compiled Templates" in docs for v0.7.3.
So what you should be doing is
window.templates = {};
$("script[type='text/mustache']").each(function() {
window.templates[this.id] = Mustache.compilePartial(this.id, this.innerHTML);
});
and your render() function
function render(data) {
var content = "";
for(var n in data) {
// don't pass partials. internal cache will be used
content += window.templates[n](data[n]);
}
return content;
}
Here is plunker to play with.
Related
Using backbone.js I'm trying to fetch a model from my server and based on that, render an underscore template. I first tried it without using the result of the api call using the following render function:
render: function(options) {
this.ticketId = options.ticketId;
var that = this;
var ticketModel = new TicketModel({'id': that.ticketId});
$.when(ticketModel.fetch()).done(function(){
console.log(ticketModel); // this outputs the correct model
});
var template = _.template($('#tab-content-template').html(), {ticketId: that.ticketId});
this.$el.html(template);
},
This works perfectly well. So I tried using the result of the api call to render the template:
render: function(options) {
this.ticketId = options.ticketId;
var that = this;
var ticketModel = new TicketModel({'id': this.ticketId});
$.when(ticketModel.fetch()).done(function(){
console.log(ticketModel);
console.log($('#tab-content-template').html());
var template = _.template($('#tab-content-template').html(), {ticketId: that.ticketId});
this.$el.html(template);
});
},
but unfortunately, this results in an error saying
Uncaugt TypeError: Cannot read property 'html' of undefined.
The weird thing is that it outputs the html in the console correctly resulting from console.log($('#tab-content-template').html());. The error I get is on the line which reads this.$el.html(template);
How can it be that it first is able to get the html(), and after that it says it can't find the property html? I'm totally stuck here.. :S
All tips are welcome!
Your issue is this no longer refers to what you think it refers to. in your code you have placed
var that = this;
this is a common pattern/trick to allow the closure to retain the context of "this" when being executed. Inside your closure "that" would now refer to what you think "this" should refer to.
Try changing "this" to "that"
$.when(ticketModel.fetch()).done(function(){
console.log(ticketModel);
console.log($('#tab-content-template').html());
var template = _.template($('#tab-content-template').html(), {ticketId: that.ticketId});
that.$el.html(template);
});
I usally make use of jQuery's proxy function that ensures when a function is executed you can be confident of the context in which it is running
$.when(ticketModel.fetch()).done($.proxy(function(){
console.log(ticketModel);
console.log($('#tab-content-template').html());
var template = _.template($('#tab-content-template').html(), {ticketId: that.ticketId});
this.$el.html(template);
},this));
oh your other question about why is $('#tab-content-template').html() working, this is because you are using JQuery directly which is in the global namespace so there for accessible where as $el is a property of your view so if you can't access your view you can't access the property.
See http://backbonejs.org/#Model-fetch - in options argument it accepts success and error callbacks:
ticketModel.fetch({
success: function(model, response, options) {
// render the template
}
});
Also if you need to use a context of current view object in this callback you can use Underscore/Lo-Dash _.bind() function to pass the context:
ticketModel.fetch({
success: _.bind(function(model, response, options) {
// Render the template using some method of the view:
this.renderMyTemplate(model);
}, this)
});
or just pass the method itself:
ticketModel.fetch({
success: this.renderMyTemplate,
error: this.handleFetchError
});
You don't need a $.when here, backbone now returns a promise for fetch call. Below code should work for you. Also consider compiling template outside render function. Compiling template is bit heavy task, should be cached once done.
var _this = this;
ticketModel.fetch().done(function () {
var template = _.template($('#tab-content-template').html(), {ticketId: that.ticketId});
_this.$el.html(template);
});
I am using meteor,
and I have a handlebar tag in my html
{{displayResult}}
in my client side JS file I write the helpers and stub method like this
Helper Function
*EDIT*
displayResult:function(){
var abc;
var apiResultDependency = new Deps.Dependency();
Meteor.call('apiresult',function(e,result){
abc=result;
apiResultDependency.changed();
});
console.log(abc);
console.log(result);
apiResultDependency.depend();
return abc;//returning nothing
}
Stub Method
Meteor.startup(function(){
return Meteor.methods({
apiresult:function(){
console.log("loading...");
}
});
});
and my server code connecting with one API and delaying results, my code is
apiresult:function(){
var response = returnAllResult();//this gets the result from outside func. working good
return response;
}
I want to take the result from server side function and I want to display in the html file
how to receive and display it. I'm not getting anything in my webpage. In my console it is printing the results.
The problem is that your template does not rerender when the data arrives from the server. The easiest way to solve this is to use reactive datasource pattern (look here), so in your client side code you would need to add something like:
var apiResultDependency = new Deps.Dependency();
var abc;
and your helper may look like this:
displayResult: function() {
if (abc === undefined) {
Meteor.call('apiresult', function(e,result) {
abc = result; // make sure this is not undefined to prevent infinite loop
apiResultDependency.changed();
});
}
apiResultDependency.depend();
return abc;
}
typeahead.js gives us the ability to render templates for our autocomplete suggestions using our engine of choice as long as the engine implements this API:
// engine has a compile function that returns a compiled template
var compiledTemplate = ENGINE.compile(template);
// compiled template has a render function that returns the rendered template
// render function expects the context to be first argument passed to it
var html = compiledTemplate.render(context);
Now dust.js has a slightly different take on the matter:
var compiled = dust.compile("Hello {name}!", "intro");
dust.loadSource(compiled);
Since I have dust.js already integrated, I'd like to use it to render typeahead's suggestions, too. I can probably wrap dust's engine object and provide the API required, but I'm wondering if there's a less intrusive and/or more elegant way to do this, e.g. by dynamically attaching the required functions to the dust object itself?
Edited to add: mixing what #user2103008 and #Simon have, here's what I'm using with typeahead-0.9.3:
function typeaheadFakeCompile(dustTemplateName) {
return function(data) {
var html;
dust.render(dustTemplateName, data, function(err, out) { html = out; })
return html;
}
}
var compiled = dust.compile('Hello {name}!', 'myTemplate');
dust.loadSource(compiled);
$('selector').typeahead({
template: typeaheadFakeCompile('myTemplate')
)};
The template parameter passed to the typeahead plugin can be either a compiled template or a string. If it's a string, the typeahead plugin will attempt to compile it. Don't do this with dust. Instead, compile the dust templates like normal, but pass the template parameter as something like:
var compiled = dust.compile('Hello {name}!', 'myTemplate');
dust.loadSource(compiled);
$('selector').typeahead({
template: fakeCompile('myTemplate')
)};
function fakeCompile (dustTemplateName) {
return {
render: function (data) {
var html;
dust.render(dustTemplateName, data, function (err,out) { html = out });
return html;
}
}
}
Typeahead should use the "compiled" template as is without attempting another compile.
EDIT
Thanks to #user2103008, fixed dust render callback function signature.
This example is borrowed from Backbone directory demo app
https://github.com/ccoenraets/backbone-directory/blob/master/web/js/utils.js#L11
// The Template Loader. Used to asynchronously load templates located in separate .html files
window.templateLoader = {
load: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (window[view]) {
deferreds.push($.get('tpl/' + view + '.html', function(data) {
window[view].prototype.template = _.template(data);
}, 'html'));
} else {
alert(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}
};
You initialize this with array of strings [views] and [callback] function.
My question is how window[view] (click above link to exact position in the code) can be checked if (as far as I see) wasn't be initialized previously? If I'm not precise please write this in comments.
If I've understood your question correctly, then when you call templateLoader.load you pass in 2 arguments; views and callback. We can assume that views is an array, since we then iterate over that array with the jQuery .each() method. The callback to .each() is passed the element of the views array that corresponds to the current iteration. That argument is named view.
So view is some arbitrary value that was stored in the views array. We then try to find a property of window with the identifier that matches the value of view. If view === "james" we are looking for window.james.
If you look at some of the views in that app you will see that they are defined like this:
window.ContactView = Backbone.View.extend({
// Some methods
});
So ContactView is a property of window, and we could call templateLoader.load like the following to load that template:
templateLoader.load(["ContactView"], someCallbackFn);
And you can see where that actually gets called in main.js.
So what's actually happening is a bunch of properties of window are defined in various other files, and then loaded by the template loader, by passing an array of identifiers to it.
I would like to cache mustache templates.
I know that I could include mustache templates directly, like this:
<script id="mustache-template" type="text/html">
<h1>{{title}}</h1>
</script>
And call those with javascript, like this:
var html, template, data;
data = {
title : "Some title"
};
template = document.getElementById('mustache-template').innerHTML;
html = Mustache.to_html(template, data);
This won't cache templates. Only way I could figure out is usage of link -tags, but how do I call template content via javascript without an ajax request?
This won't work (of course)...
HTML
<link type="text/html" href="/mustache/template.tpl" id="mustache-template" />
Javascript
document.getElementById('mustache-template').innerHTML;
This question is very interesting! I had the same problem several months ago when I started to use mustache for 'huge' front-end templating within a rails project.
I ended up with the following solution...
Mustache templates are inside a public folder :
/public/templates/_template_name.tpl
Whenever I need a template I have this helper getTemplate that does some stuff (there's some mootools, but there are comments too):
// namespace.templatesCache is an object ( {} ) defined inside the main app js file
var
needXHR = false, // for callback function
templateHTML = ""; //template html
if(!(templateHTML = namespace.templatesCache[template_name])){ //if template is not cached
templateHTML = (this.helpers.supportLocalStorage) ? localStorage.getItem(template_name) : ""; //if browser supports local storage, check if I can retrieve it
if(templateHTML === "" || templateHTML === null){ // if I don't have a template (usually, first time), retrieve it by ajax
needXHR = true;
new Request.HTML({ //or jQuery's $.get( url /*, etc */ )
url: namespace.URLS.BASE+"templates/_"+template_name+".tpl", // url of the template file
onSuccess : function(t, e, html, js){
namespace.templatesCache[template_name] = html; //cache it
if(_this.helpers.supportLocalStorage){ //and store it inside local storage, if available
localStorage.setItem(template_name,html);
}
//call callback
}
}).get();
}else{ //retrieved by localStorage, let's cache it
namespace.templatesCache[template_name] = templateHTML;
}
}
if(!needXHR){ // I retrieved template by cache/localstorage, not by Ajax
//call callback
}
and I call this helper in this way :
namespace.helpers.getTemplate('template_name', function( templateHTML ){
// the callback function
});
You can notice that first time user needs the template, there's an asynch request (you could make a sync request if u don't want to wrap some other code inside the callback)
I hope it could help and I'd love to receive feedbacks/suggestions concerning this stuff :)
You could try to load your template in an iframe that contains an HTML page(that will be cached) with all your script tags inside.
Then you can read them from the main page, or push them from the iframe to the parent window.
That is what I do when using pure.js templates
What you say it will not work, of course, because the innerHTML attribute of the liknek element will not give you the contents of the link.
You can use Chevron to load external templates from links like so:
You add in you template a link to your template file:
<link href="path/to/template.mustache" rel="template" id="templateName"/>
Then, in you JS you can render your template like so:
$("#templateName").Chevron("render", {name: "Slim Shady"}, function(result){
// do something with 'result'
// 'result' will contain the result of rendering the template
// (in this case 'result' will contain: My name is Slim Shady)
});
The docs of Chevron will give more examples