How do I load different partials dynamically using handlebars templates? - javascript

I'm loading a template with the following data:
"slides": [
{
"template": "video",
"data": {
"video": ""
}
},
{
"template": "image",
"data": {
"image": ""
}
}
]
in my template I want to loop over these slides and based on the configured template I want to load a partial
{{#each slides}}
{{> resources_templates_overlay_video }}
{{/each}}
How can I make this partial load dynamically (based on the configured template)?
I'm using the require-handlebars-plugin

As far as I can tell, hbs expects the partials to be known at compile time, which is way before you pass in your data. Let's work around that.
First, pull in your dynamic partials before rendering, something like:
// I required the main template here for simplicity, but it can be anywhere
var templates = ['hbs!resources/templates/maintemplate'], l = data.slides.length;
for (var i=0; i<l; i++ )
templates.push('hbs!resources/templates/overlay/'+data[i].template);
require(templates, function(template) {
var html = template(data);
});
And define a helper that will act as a dynamic partial
define(['Handlebars'], function (Handlebars) {
function dynamictemplate(template, context, opts) {
template = template.replace(/\//g, '_');
var f = Handlebars.partials[template];
if (!f) {
return "Partial not loaded";
}
return new Handlebars.SafeString(f(context));
}
Handlebars.registerHelper('dynamictemplate', dynamictemplate);
return dynamictemplate;
});
Finally, modify your main template to look like
{{#each slides}}
{{dynamictemplate this.template this.data}}
{{/each}}

I found the above answers a little hard to understand - they leak globals, have single character variables, and some odd naming. So here's my own answer, for my (and your) reference:
A dynamic partial using 'hbs', express.js default handlebars implementation:
I used this to make a simple blog making (article-name).md into /blog/(article-name), creating a dynamic partial:
// Create handlebars partials for each blog item
fs.readdirSync('blog').forEach(function(blogItem){
var slug = blogItem.replace('.md','')
var fileContents = fs.readFileSync('blog/'+blogItem, 'utf8')
var html = marked(fileContents)
var compiledTemplate = hbs.compile(html);
hbs.registerPartial(slug, compiledTemplate);
})
// Create 'showBlogItem' helper that acts as a dynamic partial
hbs.registerHelper('showBlogItem', function(slug, context, opts) {
var loadedPartial = hbs.handlebars.partials[slug];
return new hbs.handlebars.SafeString(loadedPartial(context));
});
Here's the route. It 404s if the partial doesn't exist, because the blog doesn't exist.
router.get('/blog/:slug', function(req, res){
var slug = req.param("slug")
var loadedPartial = hbs.handlebars.partials[slug];
if ( ! loadedPartial ) {
return res.status(404).json({ error: 'Article not found' })
}
res.render('blog', {
slug: slug
});
})
/views/blog.hbs looks like:
<div class="blog">
{{ showBlogItem slug }}
</div>

When Handlebars.partials[] returns a raw string it means the partial is not compiled.
I am not sure but my best guess is that Handlebars compiles the partial internally when it compiles the template which includes the partial. So when you use a helper to include a partial then Handlebars doesn't recognize it and it will not be compiled.
You can compile the partial yourself. Don't forget to register the compiled partial or you end up compiling every time the partial is required, which hurts performance. Something like this should work.
var template = Handlebars.partials['templatename'],
fnTemplate = null;
if (typeof template === 'function') {
fnTemplate = template;
} else {
// Compile the partial
fnTemplate = Handlebars.compile(partial);
// Register the compiled partial
Handlebars.registerPartial('templatename', fnTemplate);
}
return fnTemplate(context);

Related

can not detect current language (i18next.language) from handlebar helper?

I am using following packages for multi-languages solutions.
var i18next = require('i18next');
var i18nFsBackend = require('i18next-node-fs-backend');
var i18nMiddleware = require('i18next-express-middleware');
Since I am using handlebar as my nodejs template engine, that's I can not use i18next t('key') directly in the HTML.
so I created a handlebar helper like following
```javascript
var i18next = require('i18next');
handlebars.registerHelper('t', function(i18n_key) {
console.log(i18next.language)// always undefined, so i18next.t(i18n_key) always return default translation.
var result = i18next.t(i18n_key);
return new handlebars.SafeString(result);
});
```
However, the problem was the function is unable to detect language changed
My Workaround
app.js
```javascript
var i18nextInitCallback = function(error, t){
handlebars.registerHelper('t', function(i18n_key) {
if(app.locals.language !== i18next.language){
i18next.changeLanguage(app.locals.language);
}
var result = i18next.t(i18n_key);
return new handlebars.SafeString(result);
});
};
```
route
```javascript
router.use(function(req, res, next){
res.locals.lng = req.language;
res.app.locals.language = req.language;
next();
});
```
as you can see that on Route I assign res.app.locals.language = req.language;
and then in the handlebar helper function, I use app.locals.language to get the current language and use i18next.changeLanguage() to change the language.
and it worked.
I would like to know if I am doing it right or not?
or if there is a better solution
Using the handle function of the middleware:
app.use(middleware.handle(i18next, {
// options
}));
res.language gets already set for you and a t function fixed to user language of that request.
see: https://github.com/i18next/i18next-express-middleware/blob/master/src/index.js#L48
check out the handlebars sample: https://github.com/i18next/i18next-express-middleware/tree/master/examples/basic-handlebars

Pug call js function from another file inside template

I can't solve this for almost four hours, and i can't find any helpful documentation for this kind of problems. This is the issue, I'm using pug/jade templates and i want to call function inside pug template to transform some data
This is the main template:
/** main template */
section
each pet in pets
.pet
.photo-column
img(src= pet.photo)
.info-column
h2= pet.name
span.species= (pet.species)
p Age: #{calculateAge(pet.birthYear)} //here I need to call calculateAge function
if pet.favFoods
h4.headline-bar Favorite Foods
ul.favorite-foods
each favFood in pet.favFoods
li!= favFood
/** end main template **/
This is the external function:
/** calculateAge.js **/
module.exports = function(birthYear) {
var age = new Date().getFullYear() - birthYear;
if (age > 0) {
return age + " years old";
} else {
return "Less than a year old";
}
};
/** end calculateAge.js **/
What shell I do to make this happen?
There may be better way to handle this, but I usually do it by importing the external module and then passing it as part of template context object. That means the code that renders the template should be something like:
const calculateAge = require("calculateAge"); // change path accordingly
router.get("/main", function(){
let pageInfo = {};
pageInfo.title = "Demo";
pageInfo.calculateAge = calculateAge;
res.render("main", pageInfo);
});
Now, you can access calculateAge in your template. If this module is used a lot in most of the templates, then you should pass it as part of res.locals or app.locals so that it is available for all templates without the need to append it for every path request.
In PUG options use locals property to import functions you want to use inside your templates.
const calculateAge = require('./calculate-age');
const config = {
pug: {
locals: {
calculateAge,
},
},
};
Then you can use it in all your templates like this (please note the extra unescaping tag !{}):
p Age: !{calculateAge(pet.birthYear)}
Like in raw HTML if you want JS to execute in a sepcific part you need a script tag to enclose the js you want to use.
span.species= (pet.species)
p Age:
.script
calculateAge(pet.birthYear)
if pet.favFoods
Make your function available in pug like this:
//assuming you're using express
app.set('view engine', 'pug');
app.locals.someFunction = input => input * 5;
// or
import {someOtherFunction} from "packageOrFile";
app.locals.someOtherFunction = someOtherFunction;
In your pug you then can do
span= someFunction(10)
span= someOtherFunction(123)
This is basically what mahish wrote in his comment, but it actually answers the question satisfactory and here's the documentation.
You can write javascript with .script tag
script.
$( document ).ready(function() {
calculateAge(params)
})

How to use templates via Handlebars.precompile (vs CLI)

I'm in a .NET environment where we cannot use Node.js (requirements, silly things). We have a custom Backbone application and are using Handlebars for client side templates. Due to restrictions in this project, we're manually precompiling all of the templates with Handlebars.precompile. I've done some google-fu and searched through Stackoverflow but have not seen much useful documentation on Handlebars.precompile (the output from this is different from using the CLI tool). My issue is this: once I precompile the the Handlebars templates programmatically, how are they to be used? This implementation does not add them to the Handlebars.templates namespace.
For example, we'll take a basic template (called Stuff.handlebars):
{{!-- begin template --}}
<strong> {{test}} </strong>
{{!-- end template --}}
Here is the output using Handlebars.precompile:
function (Handlebars, depth0, helpers, partials, data) {
this.compilerInfo = [4, '>= 1.0.0'];
helpers = this.merge(helpers, Handlebars.helpers);
data = data || {};
var buffer = "",
stack1, helper, functionType = "function",
escapeExpression = this.escapeExpression;
buffer += "<strong> ";
if (helper = helpers.test) {
stack1 = helper.call(depth0, {
hash: {},
data: data
});
} else {
helper = (depth0 && depth0.test);
stack1 = typeof helper === functionType ? helper.call(depth0, {
hash: {},
data: data
}) : helper;
}
buffer += escapeExpression(stack1) + " </strong>";
return buffer;
}
When ran through the node.js handlebars compiler (handlebars stuff.handlebars -f test.js), the output is as follows:
(function () {
var template = Handlebars.template,
templates = Handlebars.templates = Handlebars.templates || {};
templates['stuff'] = template({
"compiler": [5, ">= 2.0.0"],
"main": function (depth0, helpers, partials, data) {
var helper, functionType = "function",
escapeExpression = this.escapeExpression;
return "\r\n <strong> " + escapeExpression(((helper = helpers.test || (depth0 && depth0.test)), (typeof helper === functionType ? helper.call(depth0, {
"name": "test",
"hash": {},
"data": data
}) : helper))) + " </strong>\r\n";
},
"useData": true
});
})();
My problem is this: how do I appropriate use the programmatically created Handlebars templates and pass data into them? I am currently having them namespaced to App.Templates.mytemplatename = ...output of handlebars.precompile(source)... ?
So, for example to set a template I am:
var foo = $("#foo");
$(foo).html(Handlebars.template(App.Templates.Stuff));
This outputs correctly minus data. How do I pass data into this?
Keep in mind that I am only using the handlebars.runtime.js library on the page that I'm attempting to pass data through (otherwise I wouldn't be going through all of these steps).
Edit: OK, I've found the answer.
Using our above example, I created an object called "testObj":
testObj = { test: "foo bar" };
Next, I assigned the Handlebars.template() call to a variable:
var template = Handlebars.template(App.Template.Stuff);
Finally, I passed the object in as a parameter:
template(testObj);
The output of which is: "foo bar"
Using our above example, I created an object called "testObj":
testObj = { test: "foo bar" };
Next, I assigned the Handlebars.template() call to a variable:
var template = Handlebars.template(App.Template.Stuff);
Finally, I passed the object in as a parameter:
template(testObj);
The output of which is: "foo bar"

How can I register and use a Handlebars helper with Node?

I'm using Handlebars with Node, and that works fine:
require('handlebars');
var template = require('./templates/test-template.handlebars');
var markup = template({ 'some': 'data' });
console.log(markup);
That works fine. However, I need to register and use a custom helper in my template. So, now my code looks like this:
var Handlebars = require('handlebars');
Handlebars.registerHelper('ifEqual', function(attribute, value) {
if (attribute == value) {
return options.fn(this);
}
else {
return options.inverse(this);
}
});
var template = require('./templates/test-template.handlebars');
var markup = template({ 'some': 'data' });
console.log(markup);
But now when I run my script, I get
Error: Missing helper: 'ifEqual'
So: how can I define and use a custom helper in Node?
I figured it out. I needed to do this:
var Handlebars = require('handlebars/runtime')['default'];
One what's really cool is, this even works in the browser with Browserify.
However, I found that an even better way (and probably the "correct" way) is to precompile Handlebars templates via (shell command):
handlebars ./templates/ -c handlebars -f templates.js
Then I do this:
var Handlebars = require('handlebars');
require('./templates');
require('./helpers/logic');
module.exports.something = function() {
...
template = Handlebars.templates['template_name_here'];
...
};
Here is the way i did it.
I guess nowadays its a bit different.
const Handlebars = require('handlebars');
module.exports = function(){
Handlebars.registerHelper('stringify', function(stuff) {
return JSON.stringify(stuff);
});
};
then i made a little script to call require on all the helpers just so they get ran.
// Helpers Builder
let helpersPath = Path.join(__dirname, 'helpers');
fs.readdir(helpersPath, (err, files) => {
if (err) {throw err;}
files.filter((f) => {
return !!~f.indexOf('.js');
}).forEach((jsf) => {
require(Path.join(helpersPath, jsf))();
});
});
OR the simple way
require('./helpers/stringify')();
in fact you dont even have to export it as a function you can just not export anything at all and just call require from another js file with out the function params at the end.

Compiling an Ember template with specified values

I need to get a template from Ember.TEMPLATES, compile it with a specified object and get its raw HTML value.
Ember.TEMPLATES content (generated using gruntjs) returns a function and seems to be already passed through Handlebars.template() function so for example I would have this:
Ember.TEMPLATES["test"] = Ember.Handlebars.template(function anonymous(Handlebars,depth0,helpers,partials,data) {
this.compilerInfo = [4,'>= 1.0.0'];
helpers = this.merge(helpers, Ember.Handlebars.helpers); data = data || {};
var buffer = '', hashTypes, hashContexts, escapeExpression=this.escapeExpression;
data.buffer.push("<strong>hello world ");
hashTypes = {};
hashContexts = {};
data.buffer.push(escapeExpression(helpers._triageMustache.call(depth0, "test", {hash:{},contexts:[depth0],types:["ID"],hashContexts:hashContexts,hashTypes:hashTypes,data:data})));
data.buffer.push("</strong>\n");
return buffer;
});
and would like to compile that template with new values from a JSON object.
I tried something like that based on what I've seen in Ember code:
var test = Ember.TEMPLATES['test'];
var compiled = test({ test: 'value' });
I thought it might work but it doesn't actually.
Basically I'd like to do like with standard handlebars :
Handlebars.compile('<strong>{{hello}}</strong>', { hello: 'world' });
Is there any way to compile a template with specified values, and get the HTML result using Emberjs?
Ember do some modifications in handlebars compiler to enable the use of computed properties, make templates update when model changes etc.
If you see the view render method, it does more than template(context), it use the context and some private custom data. So Handlebars.compile is diferent of Ember.Handlebars.compile and I think that compiled templates from Ember.Handlebars.compile, is not intended to be used outside of a Ember.View.
Marking script types with text/x-raw-handlebars, instead of text/x-handlebars make the template be compiled with Handlebars.compile.
The following sample will work, but without the ember features:
Template
<script type="text/x-raw-handlebars" data-template-name="custom-template">
First name: {{firstName}} <br/>Last name: {{lastName}}
</script>
Javascript
App = Ember.Application.create({
ready: function() {
var template = Ember.TEMPLATES['custom-template'];
var html = template({ firstName: 'Tom', lastName: 'Dale' });
$('body').append(html);
}
});
You can see this sample here http://jsfiddle.net/marciojunior/MC8QB/

Categories

Resources