handlebars.js template to check the condition based on the last item in an array

I am currently utilizing handlebars.js as my templating engine and am interested in creating a conditional segment that will only display if it happens to be the final item within an array located in the templates configuration object.

{
  columns: [{<obj>},{<obj>},{<obj>},{<obj>},{<obj>}]
}

While I have already integrated a helper function for performing equality, greater than, and less than comparisons, I am encountering difficulties in determining the length of the target array.

Handlebars.registerHelper('compare', function(lvalue, rvalue, options) {...})

"{{#each_with_index columns}}"+
"<div class='{{#equal index 0}} first{{/equal}}{{#equal index ../columns.length()}} last{{/equal}}'>"+
"</div>"+
"{{/each_with_index}}"

I am seeking alternative methods, a possible shortcut, or any handlebars tips that could help me avoid having to delve deep into the handlebars.js engine to find an optimal solution. Does anyone have any suggestions?

Answer №1

Starting from version 1.1.0 of Handlebars, the each helper now includes built-in support for first and last elements. This enhancement can be tracked in issue #483.

An example of this new feature can be seen in the implementation inspired by Eberanov:

{{#each foo}}
    <div class='{{#if @first}}first{{/if}}{{#if @last}} last{{/if}}'>{{@key}} - {{@index}}</div>
{{/each}}

Answer №2

A new feature has been introduced in Handlebars v1.1.0 which allows you to utilize the @first and @last booleans in the each helper to tackle this issue:

{{#each foo}}
    <div class='{{#if @first}}first{{/if}}
                {{#if @last}} last{{/if}}'>
      {{@key}} - {{@index}}
    </div>
{{/each}}

I came up with a helpful solution to achieve this:

Handlebars.registerHelper("foreach",function(arr,options) {
    if(options.inverse && !arr.length)
        return options.inverse(this);

    return arr.map(function(item,index) {
        item.$index = index;
        item.$first = index === 0;
        item.$last  = index === arr.length-1;
        return options.fn(item);
    }).join('');
});

Then you can implement it like this:

{{#foreach foo}}
    <div class='{{#if $first}} first{{/if}}{{#if $last}} last{{/if}}'></div>
{{/foreach}}

Answer №3

To address the initial element of the array, consider focusing on iterating through the data source

{{#each data-source}}{{#if @index}},{{/if}}"{{this}}"{{/each}}

The @index value is generated by the each helper, and when dealing with the first element, it will be zero and can consequently be managed by the if helper.

Answer №4

Resolution:

<div class='{{#compare index 1}} first{{/compare}}{{#compare index total}} last{{/compare}}'></div>

Utilizing assistance from the subsequent blog and gist...

https://gist.github.com/2889952

// {{#each_with_index records}}
//  <li class="legend_item{{index}}"><span></span>{{Name}}</li>
// {{/each_with_index}}

Handlebars.registerHelper("each_with_index", function(array, fn) {
  var total = array.length;
  var buffer = "";

  //Better performance: http://jsperf.com/for-vs-foreach/2
  for (var i = 0, j = total; i < j; i++) {
    var item = array[i];

    // attach an index property to the item, starting at 1, can be made configurable later
    item.index = i+1;
    item.total = total;
    // display the content inside the block
    buffer += fn(item);
  }

  // return the completed buffer
  return buffer;

});

Handlebars.registerHelper('compare', function(lvalue, rvalue, options) {

    if (arguments.length < 3)
        throw new Error("Handlerbars Helper 'compare' needs 2 parameters");

    operator = options.hash.operator || "==";

    var operators = {
        '==':       function(l,r) { return l == r; },
        '===':      function(l,r) { return l === r; },
        '!=':       function(l,r) { return l != r; },
        '<':        function(l,r) { return l < r; },
        '>':        function(l,r) { return l > r; },
        '<=':       function(l,r) { return l <= r; },
        '>=':       function(l,r) { return l >= r; },
        'typeof':   function(l,r) { return typeof l == r; }
    }

    if (!operators[operator])
        throw new Error("Handlerbars Helper 'compare' doesn't recognize the operator "+operator);

    var result = operators[operator](lvalue,rvalue);

    if( result ) {
        return options.fn(this);
    } else {
        return options.inverse(this);
    }

});

Take note that the initial index is correctly set to 1.

Answer №5

I have enhanced a helper developed by Matt Brennan to work with Objects or Arrays using the Underscore library:

Handlebars.registerHelper("foreach", function(context, options) {
  options = _.clone(options);
  options.data = _.extend({}, options.hash, options.data);

  if (options.inverse && !_.size(context)) {
    return options.inverse(this);
  }

  return _.map(context, function(item, index, list) {
    var intIndex = _.indexOf(_.values(list), item);

    options.data.key = index;
    options.data.index = intIndex;
    options.data.isFirst = intIndex === 0;
    options.data.isLast = intIndex === _.size(list) - 1;

    return options.fn(item, options);
  }).join('');
});

Usage:

{{#foreach foo}}
    <div class='{{#if @first}}first{{/if}}{{#if @last}} last{{/if}}'>{{@key}} - {{@index}}</div>
{{/foreach}}

Answer №6

In case you find yourself in a situation where you are dealing with Handlebars version below 1.1.0 (just like I did), here's a potential workaround you can consider:

Create a custom property called isLast on the objects you are looping through, and incorporate it as follows:

{{#each objectsInList}}"{{property}}": "{{value}}"{{#unless isLast}},{{/unless}}{{/each}} 

This method can be utilized to construct a JSON object.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Integrate predictive text suggestions in JavaServer Pages for efficient form filling

After some research, I have managed to solve the issue I was facing. On my jsp page, I have three text boxes. When I enter data into the first text box, it triggers a call to get.jsp to fetch data from the database and populate the second text box. However ...

Error: Unable to locate npm package

I am currently working on an Angular application that was created using Grunt and relies on Bower and NPM. Recently, I attempted to install an npm module locally. The installation resulted in the files being stored in the main application directory under ...

Error: JSON parsing stopped due to unexpected end of file while attempting to parse data

After testing with other APIs successfully, I found that this particular one is not functioning as expected. const express = require("express"); const https = require("https"); const bodyParser = require("body-parser"); const ...

Synchronous execution of functions in Node.js

I need to ensure that func2 is only called after func1 has completed its execution. { func1(); func2();// } However, the issue arises when func1() starts running and func2() does not wait for it to finish. This leads to a runtime error as func2() require ...

Reverse lookup and deletion using Mongoose

Currently, I am attempting to perform a health check on the references within one of my collections. The goal is to verify if objects being referenced still exist, and if not, remove that particular _id from the array. Despite my efforts, I have not come ...

Is there a way to have a span update its color upon clicking a link?

I have 4 Spans and I'm looking to create an interactive feature where clicking a link in a span changes the color of that span. Additionally, when another link in a different span is clicked, the color of that span changes while reverting the previous ...

Standardize the case of array keys within PHP

Is there a more efficient way to standardize the case of keys in a PHP array? While looping through and creating a new array is effective: $new = array(); foreach( $old as $key=>$value) { $key = strToLower($key); if(!array_key_exists($key,$new) ...

Signing in using Passport.js with mongoDB authentication

Apologies if this question appears redundant, but I am struggling to resolve an issue with a "MISSING CREDENTIALS" error when trying to implement user login using email and password. Despite going through numerous responses, none have provided a solution. ...

Dealing with the percentage sign in table names during data retrieval

When using React and Express to retrieve and store data in JSON format, what is the correct way to reference tables that have a percentage sign in their name? componentDidMount() { // Retrieve data from http://localhost:5000/citystats, sort by ID, t ...

Adjustable height and maximum height with overflow functionality

Currently, I am in the process of developing a task manager for my application and facing an obstacle when trying to calculate the height of a widget. My goal is to determine the maximum height (assuming a minimum height is already set) by subtracting a ce ...

Utilizing lazy evaluation, multiple functions are triggered by ng-click in succession

Successfully disabled ngClick on an element when the scope variable (notInProgress) is set to true as shown below: <a data-ng-click="notInProgress || $ctrl.setTab('renewal');</a> Now, I want to include additional functions to be execut ...

Firefox does not allow contenteditable elements to be edited if they are nested inside a parent element that is draggable

Here is a basic HTML example: <div draggable=true> <div contenteditable=true>can't edit me</div> </div> When testing in Chrome, I noticed that I was able to edit the contents of the contenteditable div, however, this functi ...

Transferring cookies across subdomains

I am facing an issue with an ajax request going from one subdomain to another, for example from sub1.example.com to sub2.example.com. Despite having a cookie set for all domains (cookie domain='.example.com'), the cookie is not being sent to the ...

The Ink CLI is anticipating the component to be a function

Currently delving into the world of the Ink library for constructing a console application in Javascript. Despite having experience with React, this is a different ball game. Some of the nuances are proving to be quite perplexing. I've managed to get ...

Issues with Login and Register functionality in a node.js application using MongoDB

I am facing an issue with my app.js. After registering for a new account, it should send the data to MongoDB and then take me directly to page2. However, instead of that, it redirects me back to the home page. Moreover, when I try to log in by entering my ...

Tips for preserving the Context API state when navigating between pages in Next.js

Currently, I am working on a project that involves using nextJs and TypeScript. To manage global states within my application, I have implemented the context API. However, a recurring issue arises each time I navigate between pages - my state re-evaluates ...

AngularJS ng-focus does not function properly with iframes

Why isn't ng-focus working with iframe in AngularJS? What am I missing? Take a look at my code: <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js"></script> <iframe src="example.com" tabindex="-1" ng-fo ...

Guide on initiating document-wide events using Jasmine tests in Angular 2/4

As stated in the Angular Testing guidelines, triggering events from tests requires using the triggerEventHandler() method on the debug element. This method accepts the event name and the object. It is effective when adding events with HostListener, such as ...

Why is it necessary to use "new" with a Mongoose model in TypeScript?

I'm a bit confused here, but let me try to explain. When creating a new mongoose.model, I do it like this: let MyModel = moongoose.model<IMyModel>("myModel", MyModelSchema); What exactly is the difference between MyModel and let newModel = ne ...

Dynamic search form technology

My HTML view includes a search form and AJAX code to handle the form request $(document).ready(function() { console.log('Ready'); $('.search-wrapper').on('click', '#find-dates', function(a) { a. ...