I\'m loading a template with the following data:
\"slides\": [
{
\"template\": \"video\",
\"data\": {
\"video\": \"\"
}
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}}
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);
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>