skip to Main Content

Right now I have all my Handlebars templates in the same html file as my site. It would look somewhat like this:

var header_template = $("#header-template").html();
var content_template = $("#content-template").html();
var footer_template = $("#footer-template").html();

$(document).ready(function() {
    // Footer
    handlebars = Handlebars.compile(footer_template);

    var content_rendered = handlebars({footer});
    $('.footer-target').replaceWith(content_rendered);

    // Header
    handlebars = Handlebars.compile(header_template);

    var content_rendered = handlebars({header});
    $('.header-target').replaceWith(content_rendered);

    // Content
    handlebars = Handlebars.compile(content_template);

    var content_rendered = handlebars();
    $('.content-target').replaceWith(content_rendered);

});
    <!-- ... head and stuff ... -->
    <body>
        <div class="wrapper">
            <div class="header-target"></div>
            <div class="content">
                <div class="content-target"></div>
            </div>
            <div class="footer-target"></div>
        </div>
    </body>

    <script id="footer-template" type="text/x-handlebars-template">
        <div class="footer font-small d-flex justify-content-center">

            <div class="text-center" style="min-width: 100%">
                {{#each footer.content}}{{this}}{{/each}}
            </div>

        </div>
   </script>

   <script id="header-template" type="text/x-handlebars-template">
      <div class="header">

            <div class="headerImg"></div>
            <ul class="nav">
                {{#each header.items}}
                <li class="nav-item">
                    <a class="nav-link">{{this}}</a>
                </li>
                {{/each}}
            </ul>

        </div>
   </script>
   
   <script id="content-template" type="text/x-handlebars-template">
       <!-- content stuff -->
   </script>

This works fine so far, but the more template scripts I add, and the bigger they get, the less readable the html file becomes.

Is there a way to store the content of the <script type="text/x-handlebars-template"></script> tags in a seperate file and load them into the html, like for example with <script src="footer-template.htm">?

2

Answers


  1. It’s basically the same like in your first code block, just with an additional AJAX request to get the template.
    So instead of <script type="text/x-handlebars-template"> you’ll have to use a simple <script> and fetch the file, then apply the template and append it to the element where you need it.

    I’ll do the example code with native fetch API, but you can use any AJAX library you want. (jQuery, Axios…)
    Just make sure that Handlebars is loaded before the script.

    <script>
    // file extension doesn't matter, just make sure it's a plain text file
    const templateFilePath = '/js/templates/head-and-stuff.handlebar';
    
    fetch(templateFilePath)
      .then((response) => response.text()) // read the text from stream
      .then((templateString) => {
    
        // compile the template
        const compiledTemplate = Handlebars.compile(templateString);
    
        // add data to the template
        const renderedTemplate = compileTemplate({
            ...
        });
        
        // add the compiled template to the DOM
        document.querySelector('#some-elem').innerHTML = renderedTemplate;
      });
    </script>
    

    This is a simplified version of the fetch, you may want to add some checks to catch errors and edgecases depending on your project.

    If you have more templates to load, you could wrap the fetch into your own function which accepts the template file path and the DOM element where it should be placed.

    Login or Signup to reply.
  2. Thinking Architecture

    For bigger projects, you may want to take a step back and think about your architecture. By loading content from external files, there are moments where the web page will render without your content, and then flash when the content is loaded, which might be a poorer user experience.

    Generally, you may want to move all the templating server-side, or use some sort of builder that can combine templates from different sources into a built index.html file that has all of your templates (like in your original example).

    Doing this client side without dynamic data has code smell and probably could be done better.

    That said, it is possible to load separate handlebars files client-side and render them.

    Loading from non-JS script elements

    You can specify attributes, like src, to script elements. For non-javascript types, it’ll get ignored by the parser, but you can manually use it later (for instance by using fetch).

    If your content only appears once, one approach could be to put the <script> tag where you want it to occur in the body, and replace it with the content later programmatically. It can look something like this, with external files for header.hbs, content.hbs, and footer.hbs:

    <body>
    <script>
    var data = {
      header: {items: ['item 1', 'item 2']},
      footer: { content: ['my footer']}
    };
    document.body.onload = function () {
      var externalHandleBarScripts = [...document.querySelectorAll('script[type="text/x-handlebars-template"]')]
        .filter(script => script.src);
      externalHandleBarScripts.forEach(async script => {
        var response = await fetch(script.src)
        var text = await response.text();
        var handlebars = Handlebars.compile(text);
        var div = document.createElement('div');
        div.innerHTML = handlebars(data);
        script.replaceWith(div);
      });
    }
    </script>
    <div class="wrapper">
      <script id="header-content" type="text/x-handlebars-template" src="header.hbs"></script>
        <div class="content">
           <script id="content" type="text/x-handlebars-template" src="content.hbs"></script>
        </div>
      <script id="footer-content" type="text/x-handlebars-template" src="footer.hbs"></script>
    </div>
    </body>
    

    Customizing per file

    If you need to define additional attributes to a specific handlebars file (like specific data to use), you could use data attributes. For instance if your header and footer have to use different data to render, you could do something like:

     <script id="header-content" type="text/x-handlebars-template"
      src="header.hbs" data-use-data-name="headerData"></script>
    <script id="footer-content" type="text/x-handlebars-template"
      src="footer.hbs" data-use-data-name="footerData"></script>
    

    And your data may look something like this:

    var data = {
      headerData: { header: {items: ['item 1', 'item 2']} },
      footerData: { footer: { content: ['my footer']}}
    };
    

    And you’d change handlebars(data) to handlebars(data[script.dataset.useDataName]):

        div.innerHTML = handlebars(data[script.dataset.useDataName]);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search