I'm using handlebars.js on a project and I'm starting to have a fair amount of templates.
For now they are stored in my main template app file, like this :
<script id="avatar_tpl" type="text/html">
bla bla bla {{var}} bla bla bla
</script>
I'm wondering if there is a way to put them in a separate file like a .js file or something, to avoid stacking them up in my source code page.
I'm aware that there are several solutions to call theses templates via Ajax, but that seems to result in too much unnecessary requests for me.
Thank you
I created and open-sourced NodeInterval for this exact same problem of too many js templates in my HTML page.
It allows you to put all your templates into a templates folder organized in whatever hierarchy you like. It has a built in watch capability so that as you modify any of these templates it automatically updates your HTML page. I use it alongside SASS for my CSS.
I use it daily with underscore templates but it should work fine with moustache templates as well:
https://github.com/krunkosaurus/NodeInterval
Couldn't you just include a js file with your templates as js variables? Not tested, just thinking here:
//in your html page
<script id="avatar_tpl" type="text/html" src="mytemplates.js"></script>
// then in your mytemplates.js file
var template_1 = "{{ content }}";
var template_2 = "{{ content }}";
// and you could use it like this back in html page
var template1 = Handlebars.compile(template_1);
var template2 = Handlebars.compile(template_2);
if you are using jquery, you could create an invisible div with id "template-holder"
then use :
$("#template-holder").load([url here])
to load the html into the div
then use :
var templatestr = $("#template-holder").find("#avatar_tpl").html()
to get the template
:)
I'm not familiar with handlebars.js but, have you tried this?:
<script id="avatar_tpl" type="text/html" src="myscript.html"></script>
I've been rolling all my scripts and templates in to one big .js file for several projects now. I use a java-based build tool, ant, to concatenate and manage various processing scripts for my js.
The biggest problem with storing large templates in javascript variables is javascript's lack of multi-line strings. I deal with this by writing my files with a python-like triple-quote syntax:
var templateVariable = '''
<div>
<div></div>
</div>
'''
I then run this custom-syntax javascript file though the python script included below, which turns it in to legal javascript:
#!/usr/bin/env python
# encoding: utf-8
"""
untitled.py
Created by Morgan Packard on 2009-08-24.
Copyright (c) 2009 __MyCompanyName__. All rights reserved.
"""
import sys
import os
def main():
f = open(sys.argv[1], 'r')
contents = f.read()
f.close
split = contents.split("'''")
print "split length: " + str(len(split))
processed = ""
for i in range(0, len(split)):
chunk = split[i]
if i % 2 == 1:
processedChunk = ""
for i,line in enumerate(chunk.split("\n")):
if i != 0:
processedChunk = processedChunk + "+ "
processedChunk = processedChunk + "\"" + line.strip().replace("\"", "\\\"").replace('\'', '\\\'') + "\"" + "\n"
chunk = processedChunk
processed = processed + chunk
f = open(sys.argv[1], 'w')
f.write(processed)
f.close()
if __name__ == '__main__':
main()
Working this way, I can code templates in more-or-less pure html, and deploy them, along with application code, inside a single .js file.
I created a Lazy Load javascript file that loads the templates only as needed. It's performing the AJAX calls, but seems to work quite well.
var Leuly = Leuly || {};
Leuly.TemplateManager = (function ($) {
var my = {};
my.Templates = {};
my.BaseUrl = "/Templates/";
my.Initialize = function (options) {
/// <summary>
/// Initializes any settings needed for the template manager to start.
/// </summary>
/// <param name="options">sets any optional parameters needed</param>
if (options && options.BaseUrl) {
my.BaseUrl = options.BaseUrl;
}
};
my.GetTemplate = function (templateName, success, baseUrl) {
/// <summary>
/// makes a request to retrieve a particular template
/// </summary>
/// <param name="templateName">name of the template to retrieve</param>
/// <param name="success">event returning the success</param>
var template = my.Templates[templateName];
if (template == null) {
template = my.LoadTemplate(templateName, success, baseUrl);
}
else {
success(template, true);
}
};
my.LoadTemplate = function (templateName, success, baseUrl) {
/// <summary>
/// makes a request to load the template from the template source
/// </summary>
/// <param name="templateName">name of the template to retrieve</param>
/// <param name="success">event returning the success</param>
var root = baseUrl == null ? my.BaseUrl : baseUrl;
$.get(root + templateName, function (result) {
my.Templates[templateName] = result;
if (result != null && success != null) {
success(result, true);
}
});
};
return my;
} (jQuery));
$(function () {
Leuly.TemplateManager.Initialize();
});
Related
I am using a framework called Framework7.
In my index.html, I have some Template7 code, like this format
<script type="text/template7" id="commentsTemplate">
{{#each this}}
<div> test this template 7 code </div>
</script>
However, I want to have this part of code into an another separated file (Just like I can have many other *.js files in, say, a static folder and refer to the file by "static/*.js).
I have tried to use a typical way to import js
<script type="text/template7" id="storiesTemplate" src="js/template.js"></script>
But it doesn't work, there is also no demo/sample code in the documentation.
Any help is appreciated!
You can do it. The idea behind is to include a HTML file in a HTML file. I can tell at least 3 ways that this can happen, but personally I fully validated only the third.
First there is a jQuery next sample is taken from this thread
a.html:
<html>
<head>
<script src="jquery.js"></script>
<script>
$(function(){
$("#includedContent").load("b.html");
});
</script>
</head>
<body>
<div id="includedContent"></div>
</body>
</html>
b.html:
<p> This is my include file </p>
Another solution, I found here and doesn't require jQuery but still it's not tested: there is a small function
My solution is a pure HTML5 and is probably not supported in the old browsers, but I don't care for them.
Add in the head of your html, link to your html with template
<link rel="import" href="html/templates/Hello.html">
Add your template code in Hello.html. Than use this utility function:
loadTemplate: function(templateName)
{
var link = document.querySelector('link[rel="import"][href="html/templates/' + templateName + '.html"]');
var content = link.import;
var script = content.querySelector('script').innerHTML || content.querySelector('script').innerText;
return script;
}
Finally, call the function where you need it:
var tpl = mobileUtils.loadTemplate('hello');
this.templates.compiledTpl = Template7.compile(tpl);
Now you have compiled template ready to be used.
=======UPDATE
After building my project for ios I found out that link import is not supported from all browsers yet and I failed to make it work on iphone. So I tried method number 2. It works but as you might see it makes get requests, which I didn't like. jquery load seems to have the same deficiency.
So I came out with method number 4.
<iframe id="iFrameId" src="html/templates/template1.html" style="display:none"></iframe>
and now my loadTemplate function is
loadTemplate: function(iframeId, id)
{
var iFrame = document.getElementById(iframeId);
if ( !iFrame || !iFrame.contentDocument ) {
console.log('missing iframe or iframe can not be retrieved ' + iframeId);
return "";
}
var el = iFrame.contentDocument.getElementById(id);
if ( !el ) {
console.log('iframe element can not be located ' + id );
return "";
}
return el.innerText || el.innerHTML;
}
How about lazy loading and inserting through the prescriptions?
(function (Template7) {
"use strict";
window.templater = new function(){
var cache = {};
var self = this;
this.load = function(url)
{
return new Promise(function(resolve,reject)
{
if(cache[url]){
resolve(cache[url]);
return true;
}
if(url in Template7.templates){
resolve(Template7.templates[url]);
return true;
}
var xhr = new XMLHttpRequest();
xhr.open('GET', url);
xhr.onload = function() {
if(this.status == 200 && this.response.search('<!DOCTYPE html>') == -1){
cache[url] = Template7.compile(this.response);
resolve(cache[url]);
}else{
reject(`Template ${url} not found`);
}
};
xhr.send();
})
}
this.render = function(url, data)
{
return self.load(url)
.then(function(tpl){
return tpl(data) ;
});
}
this.getCache = function()
{
return cache;
}
}
})(Template7);
Using :
templater.render('tpl.html').then((res)=>{ //res string })
Or :
templater.load('tpl.html').then( tpl => { Dom7('.selector').html( tpl(data) ) } )
It is possible to define your templates in .js-files. The template just needs to be a string.
Refer to this [JSFiddle] (https://jsfiddle.net/timverwaal/hxetm9rc/) and note the difference between 'template1' and 'template2'
var template1 = $$('#template').html();
var template2 = '<p>Hello, my name is still {{firstName}} {{lastName}}</p>'
template1 just extracts the content of the <script> and puts it in a string.
template2 directly defines the string
I have one handlebars template but I want to include variables from two different sources in this template.
<script id="notification-menu-item" type="text/x-handlebars-template">
I have tried to make both of the sources go to the same template id. Both files have this:
var source = $("#notification-menu-item").html();
var template = Handlebars.compile(source);
But only one of sources' variable come through to the template. Is there anyway to have one template get its {{variables}} from two different sources?
Edit: The code
This is the template:
<script id="notification-menu-item" type="text/x-handlebars-template">
<div id="navmenucontainer" class="container">
<div id="navmenuv">
<ul class="nav">
<li>Topics</li>
<li>Help</li>
{{#if logged_user}}
<li>Notifications</li>
{{#if pro}}
<li>My Data</li>
{{/if}}
{{/if}}
</ul>
</div>
</div>
</script>
pro comes from one .js file and logged_user comes from a separate .js file. Is there a way for both of these variable to be used in the same template?
You'll have to centralize the rendering of the template into one function somehow if you want to composite the data before passing it into the Handlebars.compile() function. I guess you're going to have to somehow guarantee the order in which these "plugin" js files call this new function. Otherwise it turns into something really janky like this:
Example:
Class1.js
var source = $("#notification-menu-item").html();
var html = Notification.renderNotification(source, logged_user, undefined);
if (typeof html !== 'undefined') {
$('body').prepend(html);
}
Class2.js
var source = $("#notification-menu-item").html();
var html = Notification.renderNotification(source, undefined, pro);
if (typeof html !== 'undefined') {
$('body').prepend(html);
}
Notification.js
window.Notification = function() {
var logged_user = undefined;
var pro = undefined;
return {
renderNotification: function(source, user, isPro) {
if (typeof user !== 'undefined') {
logged_user = user;
}
if (typeof pro !== 'undefined') {
pro = isPro;
}
if(typeof logged_user !== 'undefined'
&& typeof pro !== 'undefined') {
var template = Handlebars.compile(source);
var html = template({logged_user: logged_user, pro: pro});
return html;
}
}
}
Obviously this is not elegant and far from maintainable. Without getting into the specifics of how Discourse works though, I'm not sure what to tell you. At render time of the template, a full object containing all the relevant data should be passed. Subsequent calls to Handlebars.compile() would require the full set of data. Maybe should consider finding a way to split these templates up and render them into separate page elements asynchronously, or look into Partials
Disclaimer: I'm not an expert on JS or logicless templates.
I am creating a Html helper extension to render a custom file upload control. For some client side functionality to work i need this script to be present when my helper is used.
function CheckSize(fileSize,id)
{
var fileElement = document.getElementById(id);
if (fileElement .files !== undefined) {
var megaBytes = (fileInputs[0].files[0].size / 1024) / 1024;
if (megaBytes > fileSize) {
//Do Something
}
}
To ensure this script is present i will have to insert it in the Html helper as seen below.
public static class UploadControlExtensions
{
public static string UploadControl(this HtmlHelper helper,
UploadControlSettings settings)
{
string script = GetScript();
string html = string.format("<input id='{0}'
onchange='CheckSize({0},{1})'",settings.Id,setting.MaxSize);
return script + html;
}
}
This is fine if there is only one instance of my control on the page but if there are multiple instances then the same script gets inserted into the page multiple times.
Is there an elegant way to ensure only one version of my script is inserted? I know i can add it to my project seperatly but i would rather that my HtmlHelper extension class deals with all that for you.
You can store state in ViewData like:
public static class UploadControlExtensions
{
public static string UploadControl(this HtmlHelper helper, UploadControlSettings settings)
{
var viewData = helper.ViewContext.Controller.ViewData;
string script = String.Empty;
if (!viewData.ContainsKey("UploadScriptPresent"))
{
script = GetScript();
viewData.Add("UploadScriptPresent", true);
}
string html = string.format("<input id='{0}' onchange='CheckSize({0},{1})'", settings.Id, setting.MaxSize);
return script + html;
}
}
Given HTML code such :
<!-- 2. Anchor -->
<div id="anchor">This div is the <b>#anchor</b>.</div>
<!-- 3. Template -->
<script id="tpl" type="text/template">
{{#people}}
<div><img src="{{photo}}"><b>{{family}} {{name}}</b> — {{title}}, {{place}} : {{introduction}}.</div>
{{/people}}
</script>
Given JS/Handlebars such as :
<!--4. Handlebars.js slingshot -->
//4a.function creation
var slingshot = function (url, tplId, anchor) {
$.getJSON(url, function(data) {
var template = $(tplId).html();
var stone = Handlebars.compile(template)(data);
$(anchor).append(stone);
});
}
slingshot('data.json', '#tpl', '#anchor'); // since url = 'data.json' , we can use both notations.
How to externalize the my 3. Template (#tpl) into a proper .txt text file (or other extension) ? How to load it back ? so I may use the same template into various .html webpages.
Full code : http://bl.ocks.org/hugolpz/8075193 / http://bl.ocks.org/hugolpz/raw/8075193/
Put the following template content into a file named test.handlebars
{{#people}}
<div><img src="{{photo}}">
<b>
{{family}} {{name}}
</b> — {{title}},
{{place}} : {{introduction}}.
</div>
{{/people}}
Write a function which will use the template as below
function getTemplate(name) {
if (Handlebars.templates === undefined || Handlebars.templates[name] === undefined) {
$.ajax({
url : name + ".handlebars",
success : function(data) {
if (Handlebars.templates === undefined) {
Handlebars.templates = {};
}
Handlebars.templates[name] = Handlebars.compile(data);
},
async : false
});
}
return Handlebars.templates[name];
}
In the main program you can write the below statement to insert the template contents into div with id="anchor", as shown below
var Template = getTemplate("test")
this.$("#anchor).append(Template(data));
where data is the contents of a json file or some db query output which will give you the values meant for the following attributes in json format
people, twitter, name, family, photo, title, place, introduction
I'm assuming you have already compiled your template. So you can use the technique I have described in Bootstrapping Multiple Instances of an HandlebarsJS Template Into a Page.
Hook and libs
Place this in your index.html:
<div class="hook" data-json="data/whatever.json"></div>
and the JavaScript libs
<!-- Helper to inject data-set in templates instance -->
<script src="scripts/template-loader.js"></script>
<!-- Get the (compiled) template -->
<script src="scripts/myTemplate.hbs.js"></script>
template-loader.js helper
$(function(){
'use strict';
var compiledTemplate = myApp.Templates['app/templates/myTemplate.hbs'];
$('.hook').each(function(i, h){ # h = current hook
var url = $(h).data('json'); # data-set's url
$.getJSON(url).then(function (json) { # fetch data-set
var tpl = compiledTemplate( json ); # inject data into template
$(h).html(tpl); # inflate template in page
});
});
});
Please read the complete article for further details.
I am trying to build a template builder using http://ejohn.org/blog/javascript-micro-templating
My html has this script tag
<script type="text/html" id="item_tmpl">
<div>
<div class="grid_1 alpha right">
</div>
<div class="grid_6 omega contents">
<p><b><%=AdTitle%>:</b> <%=AdTitle%></p>
</div>
</div>
</script>
<script src="${URLUtils.staticURL('/js/shoptheAd.js')}"type="text/javascript"></script>
The Script contains the following code
(function(app){
if (app) {
var cache = {};
this.tmpl = function tmpl(str, data){
// Figure out if we're getting a template, or if we need to
// load the template - and be sure to cache the result.
var fn = !/\W/.test(str) ?
cache[str] = cache[str] ||
tmpl(document.getElementById(str).innerHTML) :
// Generate a reusable function that will serve as a template
// generator (and which will be cached).
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
// Introduce the data as local variables using with(){}
"with(obj){p.push('" +
// Convert the template into pure JavaScript
str
.replace(/[\r\t\n]/g, " ")
.split("<%").join("\t")
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)%>/g, "',$1,'")
.split("\t").join("');")
.split("%>").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
// Provide some basic currying to the user
return data ? fn( data ) : fn;
};
var sitecoresuggestions = {
"suggestions": [
{
"AdTitle": "CheckAd",
"AdDescription": "",
"AdImageUrl": "http://demo-kiehls.loreal.photoninfotech.com/~/media/Advertisement Images/emma-watson-3.ashx",
"Count": 2,
"Hit": 0
},
{
"AdTitle": "CheckAd",
"AdDescription": "",
"AdImageUrl": "http://demo-kiehls.loreal.photoninfotech.com/~/media/Advertisement Images/kate2.ashx",
"Count": 2,
"Hit": 0
}
]
} ;
var show_user = tmpl("item_tmpl"), html = "";
for ( var i = 0; i < sitecoresuggestions.suggestions.length; i++ ) {
html += show_user( sitecoresuggestions.suggestions[i] );
}
console.log(html);
} else {
// namespace has not been defined yet
alert("app namespace is not loaded yet!");
}
})(app);
When the show_user = tmpl("item_tmpl") is executed
i get the error TypeError: document.getElementById(...) is null
on debugging i have figured out that due to some reason
<script type="text/html" id="item_tmpl">
<div>
<div class="grid_1 alpha right">
</div>
<div class="grid_6 omega contents">
<p><b><%=AdTitle%>:</b> <%=AdTitle%></p>
</div>
</div>
</script>
does not get loaded in the browser any ideas why it is not getting loaded even though it is included inside the head tag or any other pointers for the cause of the error
Per the post:
Quick tip: Embedding scripts in your page that have a unknown content-type (such is the case here - >the browser doesn't know how to execute a text/html script) are simply ignored by the browser - and >by search engines and screenreaders. It's a perfect cloaking device for sneaking templates into >your page. I like to use this technique for quick-and-dirty cases where I just need a little >template or two on the page and want something light and fast.
So the page doesn't actually render the HTML, and I would assume you would only have reference to it in the page so that you can extract and apply to other objects or items. And as the blogger states you would use it like:
var results = document.getElementById("results");
results.innerHTML = tmpl("item_tmpl", dataObject);