Search code examples
javascripthandlebars.js

How to include a unique change in div structure/styling in my Handlebars template?


Still learning Handlebars so not sure if this question is clear. I have an array of people objects. My template works fine for the divs that will hold each person's image who are placed in identically structured containers, until it iterates to specific people.

The top row renders to the expected equal-sized images which total in four, before it wraps to the second, with the different grid I cannot figure out how to template, before going on to the third row which continues back to the four-image row that my template works well with. There are variations near the bottom of this giant grid with different sizes as well for specific people in my array of objects that I'll need to figure out, too.

From tutorials, I thought a partial would help but not sure how to prevent the {{#each}} from applying a partial for every context. How do I do this, or what helpers/functions do I need to achieve this?

Example code:

HTML expected

<div class="person-image-grid">
  ..stuff
</div> (x4)
<div class="different-grid">
   <div class="person-image-grid">
     ..stuff
   </div>
   <div class="half-width video">
     A bigger div, about half the width of the page, with video of person
   </div>
   <div class="person-image-grid">
     ..stuff
   </div>
</div>
<div class="person-image-grid">
  ..stuff
</div> (x4)

My template

{{#each person}}
  <div class="person-image-grid">
      <div class="person-image-grid_picture">
          <img class="person-image-grid_image" src="personImages/{{firstName}}_{{lastName}}/{{firstName}}{{lastName}}.jpg">
      </div>
      <p class="person-image-grid_name">{{firstName}} {{lastName}}</p>
  </div>
{{/each}}

JS

function _templateGridModal() {
  var templateGridScript = document.getElementById("student-image-grid-template").innerHTML,
  parentGrid = document.getElementsByClassName("student-image-grid")[0];

  var theTemplateGrid = Handlebars.compile(templateGridScript);

  var obj = { 
   people: [
     {"firstName": "Joe", "about": "stuff", "quote": "a quote here"},
     {"firstName": "John", "about": "stuff" //etc},
     {"firstName": "Mary", "about": "stuff" //etc},
     {"firstName": "Tom", "about": "stuff" //etc}
    //and so on
   ] 
  };

  var theCompiledTemplateGrid = theTemplateGrid(obj);
  parentGrid.innerHTML = theCompiledTemplateGrid;
}

Solution

  • I would avoid trying to put all the complicated conditions and counters into the template. The philosophy behind Handlebars is that logic should not be in the view layer, and so I think you will find yourself writing a lot of custom helper code in order to get things to work this way. Instead, I would recommend chunking your people list before it gets to the template. You could have an array of alternating "row" objects that hold the data for each row - whether or not it is a single item row and the columns (people) that are to render in that row. The rows array would look like the following.

    var rows = [
        {
            is_single: false,
            people: [] //Array[4]
        },
        {
            is_single: true,
            people: [] //Array[1]
        }
    ];
    

    At this point the most challenging part is to figure out the math required to dynamically build the rows array from the people array. I have created an implementation which may or may not be the best way to do it:

    // 2 rows for every 5 people + 1 row for any remainders
    var num_rows = Math.floor(people.length / 5) * 2 + (people.length % 5 ? 1 : 0);
    
    var rows = [];
    
    for (var i = 0; i < num_rows; i += 1) {
        var is_single = Boolean(i % 2);
        var start_index = is_single ? (Math.floor(i/2) + 4 * (Math.floor(i/2) + 1)) : (2 * i + Math.floor(i/2));
        var end_index = is_single ? start_index + 1 : start_index + 4;
    
        rows.push({
            is_single: is_single,
            people: people.slice(start_index, end_index)
        });
    }
    

    All we have left to do is to re-write our template to iterate over our rows object and to render the applicable markup based on is_single flag in each row:

    {{#each rows}}
        {{#if is_single}}
            <div class="different-grid">
                {{#with (lookup people 0)}}
                    <div class="person-image-grid">
                        {{firstName}}
                    </div>
                {{/with}}
                <div class="half-width video">
                    A bigger div, about half the width of the page, with video of person
                </div>
                <div class="person-image-grid">
                    ..stuff
                </div>
            </div>
        {{else}}
            {{#each people}}
                <div class="person-image-grid">
                    {{firstName}}
                </div>
            {{/each}}
        {{/if}}
    {{/each}}
    

    I have created an example fiddle for reference.