admin管理员组

文章数量:1180537

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

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

Share Improve this question edited Apr 10, 2018 at 13:31 mikemaccana 123k110 gold badges427 silver badges529 bronze badges asked Nov 15, 2012 at 11:25 Daan PoronDaan Poron 2,7585 gold badges30 silver badges33 bronze badges 2
  • Sure would like to know too. So far research has not turned up a solution. – foxdonut Commented Nov 15, 2012 at 15:28
  • 3 An update from 2015: it looks like handlebars now supports dynamic partials. handlebarsjs.com/partials.html – jtfairbank Commented Sep 3, 2015 at 4:21
Add a comment  | 

4 Answers 4

Reset to default 15

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);

This is documented in official documentation. Simplest solution for me is to use Handlebars sub-expressions combined with one helper for complex solutions, and a lookup for simple stuff.

Solution 1: If you partial corresponds to template name:

{{#each slides}}
    {{> (lookup . template) }}
{{/each}}
  • . refers current item being iterated (see lookup docs)
  • template refers to "template" key in your current slide's dynamic data.

Those two combined mean lookup template key in current slide being iterated.


Solution 2: If you partials consists of appendix, prefix, to a static string:

Prepare a helper that would do concatenation for you:

handlebars.registerHelper('concat', function(string1, string2, options) {
  return string1 + string2;
})

You could make this function concatenate string in any way you want to help you get the string format that corresponds to your template name, this is just a simple example.

{{#each slides}}
    {{> (concat "resources_templates_overlay_" this.template) }}
{{/each}}

Here concat helper gets two parameters:

  1. "resources_templates_overlay_"
  2. this.template which is dynamic part

Which are then used to build your string and pass it to partial logic ending up with: resources_templates_overlay_video if template has "video" as value, and similar for image.

Using second solution you can load any partial provided the dynamic part is there.


本文标签: javascriptHow do I load different partials dynamically using handlebars templatesStack Overflow