How to cache mustache templates? - javascript

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

Related

NODE.JS accessing functions within an EJS template

I have a scripts.js file that includes a function inside that i want to access within an EJS templates.
in the templates i included a header, in the header I added a script rel to scripts.js
<script src="/scripts/scripts.js" type="text/javascript"></script>
I tested it though console.log("test") and when the template is being called I see "test" appears in the console.
when I try to call an actual function (verify_data) within the EJS template i get an error verify_data is not defined.
the error code within the EJS looks like this:
<p><em>Submited By <%= verify_data(results.user.username) %></em></p>
the function check if the argument passed is null/undefined, if yes the data returns if not "n/a" string returns instead.
How do i access JS functions directly within EJS template ?
Thanks,
Assuming you're not using a SPA framework, you need to add a tag which will be either replaced by the function or to contain the data you want to store.
Look at this code snippet
<p><em>Submited By <span id='userData'></span></em></p>
<script>
//This is the script.js
function verify_data(userName) {
return `My data with '${userName}'`;
}
</script>
<script>
let data = verify_data("EleFromStack"); // assuming <%=results.user.username%> = "EleFromStack"
document.querySelector("#userData").textContent = data;
</script>

Render HTML file content and assign it to an Object

This is what my initial js looks like
// template.js
file: {
template : '<div><h2>Template</h2><p>My paragraph</p></div>';
}
But I need to create this as a separate html file.
<!-- template.html -->
<div>
<h2>Template</h2>
<p>My paragraph</p>
</div>
Now I need to import and assign it to an object (template) of a script. Any ideas how to do this?
// template.js
file: {
template : 'Content Here';
}
You will have to get it via AJAX. This will be a pseudo-promise (see: Promise and $.get()) that is resolved with the contents of the file:
$.get(templateUrl).then(...)
So a good example would be:
// assuming `file` is inside `options`
if (options.file.template == null && options.file.templateUrl != null) {
$.get(options.file.templateUrl).then(function(html) {
options.file.template = html
// continue doing stuff with the newly received html...
});
}
Notice how I separated them into template and templateUrl, but you don't have to do that.
Note: if you try to use to use the result outside the promise's callback, you will probably get undefined when you try to access it (unless some time has passed) so you might need to organize your code to accommodate to that.

Order Levels on Underscore Templates, making sure data form Backbone Collection loads in set order?

I have an Underscore.JS template loading in basic text data from my database. These load into two div tags. But I think (I am not sure here, please correct me if I am wrong), that depending on what table Backbone gets first depends on the order of which these div tags are displayed in.
So (a), I have come up with a answer, I just wanted to know weather or not this was the best way of doing it by 'chaining' (if that is the right way of calling it) the success calls, within the same Backbone View, this loads both divs into the same Underscore template. So this is my code currently,
var TextEditView = Backbone.View.extend({
inititalize: function() {
this.listenTo(this.BasicTextModel, "change", this.render);
},
el: $(".BasicTextTemplate"), //Template loader placeholder
render: function() {
var that = this;
var AboutText = new TextAboutCollection(); //Get About page text
var HomeText = new TextHomeCollection(); //Get Home page text
HomeText.fetch({
success: function(Text) {
var GetHomeTxt = _.template( $('script.BasicText').html(), { Text: Text.toJSON() } ); //Load About text into template
that.$el.append(GetHomeTxt);
that.trigger('ChangeTxt', that);
AboutText.fetch({
success: function(Text) {
var GetAboutTxt = _.template( $('script.BasicText').html(), { Text: Text.toJSON() } ); //Load About text into template
that.$el.append(GetAboutTxt);
that.trigger('ChangeTxt', that);
} //End of Success Call to AboutText
}); //End of .fetch for AboutText
}}); //End of Success & .fetch calls for HomeText
} //End of render view function
}); //End of TextEditView
The AboutText is only loaded after the success call for hometext fetch is done, right? Even if I do not get this 100%, it does seem to work for now.
And (b), is there a simple way of adding a 'level' or ID to each div tag making sure Underscore loads each tag in the order I want? Or do I have this completely wrong and should load both these Collections into their own view pointing to there own template? If so I do not understand the point of the template? The way I have them now, I only have the one template that I have re-used!
Please if I am wrong, please correct me
Thanks,
EDIT of the EDIT:
model = new Model({_id:id})
var fetched = model.fetch();
// wait for the model to be fetched
$.when(fetched).then(function(){
view = new View({model:model})
app.content.show(view)
});
from https://stackoverflow.com/a/13601074/2535516
EDIT :
This refers about the comment.
About building code, i think u refer as organization of a backbone project (correct me if i'm wrong). If so, the way i am doing is write my code by module. A module is basically : a BackboneModel/Collection, View, and underscore template.
Architecture :
/
main.js
About/
-- about.html <-- contains the underscore template
-- aboutView.js
-- aboutModel.js
-- aboutCollection.js
Menu/
-- menu.html
-- menuView.js
-- menuModel.js
-- menuCollection.js
And i load it through an AMD lib, mostly require.js
This architecture is a personal, what you will mostly see is :
/
main.js
View/
-- about.js
-- menu.js
Model/
-- aboutModel.js
-- menuModel.js
Collection/
-- aboutCollection.js
-- menuCollection.js
A good example is the TODOMvc architecture
Your code will work, but this is not very readable and will go worse if you'll have to add some fetch :
HomeText.fetch({
AboutText.fetch({
Module1Text.fetch({
Module2Text.fetch({
});
});
});
});
This is call the pyramid of doom.
To avoid this, use promises.
As a note, jquery do promises

Error on Mustache.render using pre-parsed templates

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.

What's the least intrusive way to make dust.js able to render typeahead.js templates?

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.

Categories