Handlebars.js: {{#switch}} helper

Created on 22 Dec 2014  ·  15Comments  ·  Source: handlebars-lang/handlebars.js

This is very useful and I think it'd be best if it were native to the library:

{{#switch state}}
    {{#case "page1" "page2"}}toolbar{{/case}}
    {{#case "page1" break=true}}page1{{/case}}
    {{#case "page2" break=true}}page2{{/case}}
    {{#case "page3" break=true}}page3{{/case}}
    {{#default}}page0{{/default}}
{{/switch}}

Most helpful comment

This page is currently the 3rd Google search result for "Handlebars switch helper". For the benefit of people arriving here from a search engine, here's an implementation by Chris Montrois which supports single-value case clauses such as {{#case "page1"}}:

Handlebars.registerHelper("switch", function(value, options) {
    this._switch_value_ = value;
    var html = options.fn(this); // Process the body of the switch block
    delete this._switch_value_;
    return html;
});

Handlebars.registerHelper("case", function(value, options) {
    if (value == this._switch_value_) {
        return options.fn(this);
    }
});

Here's an improved case helper which supports clauses with a variable number of values, such as {{#case "page1" "page2"}}:

Handlebars.registerHelper("case", function() {
    // Convert "arguments" to a real array - stackoverflow.com/a/4775938
    var args = Array.prototype.slice.call(arguments);

    var options    = args.pop();
    var caseValues = args;

    if (caseValues.indexOf(this._switch_value_) === -1) {
        return '';
    } else {
        return options.fn(this);
    }
});

All 15 comments

This is something that can be achieved (in a hacky manner) using standard helpers. We wish to keep the builtin helpers relatively lightweight, in stead allowing 3rd parties to implement the helpers they need, in the manner that suits them best, using the helper API.

This page is currently the 3rd Google search result for "Handlebars switch helper". For the benefit of people arriving here from a search engine, here's an implementation by Chris Montrois which supports single-value case clauses such as {{#case "page1"}}:

Handlebars.registerHelper("switch", function(value, options) {
    this._switch_value_ = value;
    var html = options.fn(this); // Process the body of the switch block
    delete this._switch_value_;
    return html;
});

Handlebars.registerHelper("case", function(value, options) {
    if (value == this._switch_value_) {
        return options.fn(this);
    }
});

Here's an improved case helper which supports clauses with a variable number of values, such as {{#case "page1" "page2"}}:

Handlebars.registerHelper("case", function() {
    // Convert "arguments" to a real array - stackoverflow.com/a/4775938
    var args = Array.prototype.slice.call(arguments);

    var options    = args.pop();
    var caseValues = args;

    if (caseValues.indexOf(this._switch_value_) === -1) {
        return '';
    } else {
        return options.fn(this);
    }
});

Very nice, short and sweet. I'd come across much longer ones in the past.

A couple of things to add might be {{#default}} {{/default}} and {{#case "value" break=true}}.

@stevenvachon As requested ;)

module.exports = {
    switch: function(value, options) {
        this._switch_value_ = value;
        this._switch_break_ = false;
        var html = options.fn(this);
        delete this._switch_break_;
        delete this._switch_value_;
        return html;
    },
    case: function(value, options) {
        var args = Array.prototype.slice.call(arguments);
        var options    = args.pop();
        var caseValues = args;

        if (this._switch_break_ || caseValues.indexOf(this._switch_value_) === -1) {
            return '';
        } else {
            if (options.hash.break === true) {
                this._switch_break_ = true;
            }
            return options.fn(this);
        }
    },
    default: function(options) {
        if (!this._switch_break_) {
            return options.fn(this);
        }
    }
};

@Billy- Where should we include this?

@jimkoul These are helper functions so it depends on your set up.

My set up (with Gulp + gulp-hb) supports specifying a glob pattern for js files which export helper functions, as above, so it looks like this:

// ...
.pipe(handlebars({
  helpers: 'handlebars/helpers/*.js'
})
// ...

If you're still not sure do a little research into how helper functions work with handlebars, and how to implement them with whatever implementation of handlebars you are using.

It would be nice if we could pass variable thru the switch name and not have to write a complete {{assign}} thru each. That way the option becomes the variable value.

Additionally I'm also getting an undefined message even though the variable is changing correctly when the case is met.

This is what I have for the helper

"use strict";
Handlebars.registerHelper("switch", function(value, options) {
    this._switch_value_ = value;
    this._switch_break_ = false;
    var html = options.fn(this);
    delete this._switch_break_;
    delete this._switch_value_;
    return html;
});

Handlebars.registerHelper("case", function(value, options) {
    var args = Array.prototype.slice.call(arguments);
    var options    = args.pop();
    var caseValues = args;

    if (this._switch_break_ || caseValues.indexOf(this._switch_value_) === -1) {
        return '';
    } else {
        if (options.hash.break === true) {
            this._switch_break_ = true;
        }
        return options.fn(this);
    }
});

Handlebars.registerHelper("default", function(options) {
    if (!this._switch_break_) {
        return options.fn(this);
    }
});

This is what I have in my hbs file:

{{#assign "testParam"}}foo{{/assign}}
{{#switch testParam}}
    {{#case "boo"}}{{#assign "testParam"}}case1 has been met{{/assign}}{{/case}}
    {{#case "foo" break=true}}{{#assign "testParam"}}case2 has been met{{/assign}}{{/case}}
    {{#case "tried" break=true}}{{#assign "testParam"}}case3 has been met{{/assign}}{{/case}}
    {{#case "bwahahaha" break=true}}{{#assign "testParam"}}case4 has been met{{/assign}}{{/case}}
    {{#default break=true}}{{#assign "testParam"}}nothing matched{{/assign}}{{/default}}
{{/switch}}

{{#ttpartial "testSwitch.content"}}
        {{testParam}}
{{/ttpartial}}

or some reason the above makes the default always take precedence even when one of the cases above are met.

Here's what I use:

Handlebars.registerHelper('switch', function(name, value, options) {
    this['_switch_value_' + name] = value;
    this['_switch_break_' + name] = false;
    var html = options.fn(this);
    delete this['_switch_break_' + name];
    delete this['_switch_value_' + name];
    return html;
});
Handlebars.registerHelper('case', function(name, value, options) {
    var args = Array.prototype.slice.call(arguments);
    var options    = args.pop();
    var caseValues = args;

    if ( this['_switch_break_' + name] || caseValues.indexOf(this['_switch_value_' + name]) === -1) {
        return '';
    } else {
        this['_switch_break_' + name] = true;
        return options.fn(this);
    }
});
Handlebars.registerHelper('default', function(name, options) {
    if ( !this['_switch_break_' + name] ) {
        return options.fn(this);
    }
});

The switch are named, so it is possible to branch them (imbrication ? not sure about the word...)
Also, break is always done when a condition is met

ie:

    {{#switch "1" "aaa"}}
        {{#case "1" "aaa"}}
            {{#switch "2" "bbb"}}
                {{#case "2" "bbb"}}ok{{/case}}
            {{/switch}}
        {{/case}}
        {{#default "1"}}nok{{/default}}
    {{/switch}}

I'm sure I'm failing to understand some of how this works, so I apologize for my confusion, but... Is it possible for the switch to work within an each? I've been trying it out, but I keep getting errors.

Here's what I've got:

{{#each columns}}
    {{#switch this}}
        {{#case 'foo'}}
        {{/case}}
        {{#case 'bar'}}
        {{/case}}
    {{/switch}}
{{/each}}

But I'm getting this:

Uncaught TypeError: Cannot create property '_switch_value_' on string 'name'

It seems to me that this must be something other than what is expected, but I don't really know.

Looks like I was able to get it working by changing the switch as follows:

Handlebars.registerHelper({
    switch: function(value, options) {
        return options.fn({
            _switch_value_: value,
            _switch_break_: false
        });
    },
    //...
});

But I'm guessing I'm losing something for other cases. This said, I could always attach these values to options, and I think it would work.

@Billy- @a-le ?

@infinityplusone unfortunately I haven't used handlebars properly in years so I imagine that the helper implementation may have changed heavily since I wrote that, which is probably why it wasn't working as expected. I'm not sure though, sorry to disappoint.

@infinityplusone If you use my version, you have to "name" the switch.
So, with your example:

{{#each columns}}
    {{#switch "myswitch" this}}
        {{#case "myswitch" 'foo'}}
        {{/case}}
        {{#case "myswitch" 'bar'}}
        {{/case}}
    {{/switch}}
{{/each}}

for nested switch block

Handlebars.__switch_stack__ = [];

Handlebars.registerHelper( "switch", function( value, options ) {
    Handlebars.__switch_stack__.push({
        switch_match : false,
        switch_value : value
    });
    var html = options.fn( this );
    Handlebars.__switch_stack__.pop();
    return html;
} );
Handlebars.registerHelper( "case", function( value, options ) {
    var args = Array.from( arguments );
    var options = args.pop();
    var caseValues = args;
    var stack = Handlebars.__switch_stack__[Handlebars.__switch_stack__.length - 1];

    if ( stack.switch_match || caseValues.indexOf( stack.switch_value ) === -1 ) {
        return '';
    } else {
        stack.switch_match = true;
        return options.fn( this );
    }
} );
Handlebars.registerHelper( "default", function( options ) {
    var stack = Handlebars.__switch_stack__[Handlebars.__switch_stack__.length - 1];
    if ( !stack.switch_match ) {
        return options.fn( this );
    }
} );
{{#switch state}}
    {{#case "page1" "page2"}}page 1 or 2{{/case}}
    {{#case "page3"}}page3{{/case}}
    {{#case "page4"}}page4{{/case}}
    {{#case "page5"}}
            {{#switch s}}
                {{#case "3"}}s = 3{{/case}}
                {{#case "2"}}s = 2{{/case}}
                {{#case "1"}}s = 1{{/case}}
                {{#default}}unknown{{/default}}
            {{/switch}}
    {{/case}}
    {{#default}}page0{{/default}}
{{/switch}}
var data = {
    state : 'page5',
    s : '1'
};

var html = template( data );

How about this?
just if you want to convert key to value.

````javascript
module.exports = function(input, cases, values) {
const caseArray = cases.split(',')
const valueArray = values.split(',')
const index = caseArray.indexOf(input)

return valueArray[index]
};
handlebars
{{switch "Y" "Y,N,D", "YES,NO,DELETED"}}
````

Was this page helpful?
0 / 5 - 0 ratings

Related issues

fcpauldiaz picture fcpauldiaz  ·  4Comments

rizen picture rizen  ·  6Comments

morgondag picture morgondag  ·  5Comments

ShintaroOkuda picture ShintaroOkuda  ·  7Comments

ustun picture ustun  ·  6Comments