How to set selected select option in Handlebars template

Javascript

Javascript Problem Overview


With a Handlebars.js template like this...

<select>
    <option value="Completed">Completed</option>
    <option value="OverDue">OverDue</option>
    <option value="SentToPayer">SentToPayer</option>
    <option value="None">None</option>
</select>

... and data like this...

{
    "id"     : 1,
    "name"   : "World"
    "status" : "OverDue",
    "date"   : "2012-12-21"
}

I want to render HTML like this.

<select>
    <option value="Completed">Completed</option>
    <option value="OverDue" selected="selected">OverDue</option>
    <option value="SentToPayer">SentToPayer</option>
    <option value="None">None</option>
</select>

Which way is the easiest?

Javascript Solutions


Solution 1 - Javascript

I found a lot of over complicated solutions and decided to write my own using a Handlebars helper.

With this partial (using Jquery) ...

    window.Handlebars.registerHelper('select', function( value, options ){
        var $el = $('<select />').html( options.fn(this) );
        $el.find('[value="' + value + '"]').attr({'selected':'selected'});
        return $el.html();
    });

You can wrap selects in your Handlebars template with {{#select status}}...

<select>
    {{#select status}}
    <option value="Completed">Completed</option>
    <option value="OverDue">OverDue</option>
    <option value="SentToPayer">SentToPayer</option>
    <option value="None">None</option>
    {{/select}}
</select>

and end up with this...

<select>
    <option value="Completed">Completed</option>
    <option value="OverDue" selected="selected">OverDue</option>
    <option value="SentToPayer">SentToPayer</option>
    <option value="None">None</option>
</select>

Presto!

Solution 2 - Javascript

I just had a similar need as the OP--with a static set of select options, but a dynamic selected value. I really like @janjarfalk's solution, but I'm using node.js and don't have jQuery pulled in. So, I put together my own variation based on RegExp's. Hope this is helpful to others.

Handlebars helper:

hbs.registerHelper('select', function(selected, options) {
    return options.fn(this).replace(
        new RegExp(' value=\"' + selected + '\"'),
        '$& selected="selected"');
});

Handlebars template:

<select>
    {{#select CurrentSort}}
    <option value="1">Most Recent First</option>
    <option value="2">Most Recent Last</option>
    <option value="3">Highest Score First</option>
    <option value="4">Highest Score Last</option>
    <option value="5">Most Comments</option>
    <option value="6">Fewest Comments</option>
    <option value="7">Most Helpful Votes</option>
    <option value="8">Fewest Helpful Votes</option>
    {{/select}}
</select>

You can tweak the helper to work even if you don't use the value attribute--just adjust the regexp to search the element text, and do the string replacement before the matched text.

Solution 3 - Javascript

I saw the extremely clever solution posted by @janjarfalk and realized it didn't work for options defined without a value attribute (such as <option>Value</option>). My application needed that, and I wanted a helper done in vanilla JavaScript for performance, so I came up with the following.

This solution will support <option>Both a label and a value</option> in addition to <option value="aValue">A label</option> and will be much faster as it doesn't use jQuery.

Handlebars.registerHelper('select', function(value, options) {
    // Create a select element 
    var select = document.createElement('select');

    // Populate it with the option HTML
    select.innerHTML = options.fn(this);

    // Set the value
    select.value = value;

    // Find the selected node, if it exists, add the selected attribute to it
    if (select.children[select.selectedIndex])
        select.children[select.selectedIndex].setAttribute('selected', 'selected');

    return select.innerHTML;
});

Usage:

<select>
    {{#select status}}
    <option>Option 1</option>
    <option>Option 2</option>
    <option value="Option 3">Option 3 (extra info)</option>
    <option value="Option 4">Option 4 (more extra)</option>
    {{/select}}
</select>

Solution 4 - Javascript

Works for me

<select>
    <option value="{{status}}" hidden="hidden" selected="selected">{{status}}</option>
    <option value="Completed">Completed</option>
    <option value="OverDue">OverDue</option>
    <option value="SentToPayer">SentToPayer</option>
    <option value="None">None</option>
</select>

Solution 5 - Javascript

I've had problems with the "select block" approach when using the "each" helper to build something dynamic, due to the context.

Here is my solution:

  Handlebars.registerHelper('option', function(value, label, selectedValue) {
    var selectedProperty = value == selectedValue ? 'selected="selected"' : '';
    return new Handlebars.SafeString('<option value="' + value + '"' +  selectedProperty + '>' + label + "</option>");
  });

And the template:

<select>
  {{#each status}}
    {{option id name ../statusId}}
  {{/each}}
</select>

Solution 6 - Javascript

Improved answer of @lazd to select first option when nothing matches.

Handlebars.registerHelper('select', function(value, options) {
	// Create a select element 
	var select = document.createElement('select');


// Populate it with the option HTML
$(select).html(options.fn(this));

//below statement doesn't work in IE9 so used the above one
//select.innerHTML = options.fn(this); 

	// Set the value
	select.value = value;

	// Find the selected node, if it exists, add the selected attribute to it
	if (select.children[select.selectedIndex]) {
		select.children[select.selectedIndex].setAttribute('selected', 'selected');
	} else { //select first option if that exists
		if (select.children[0]) {
			select.children[0].setAttribute('selected', 'selected');
		}
	}
	return select.innerHTML;
});

Usage remains same:

<select>
    {{#select status}}
    <option>Option 1</option>
    <option>Option 2</option>
    <option value="Option 3">Option 3 (extra info)</option>
    <option value="Option 4">Option 4 (more extra)</option>
    {{/select}}
</select>

Solution 7 - Javascript

This might take more code in the template, but it is easier to read:

.js

Handlebars.registerHelper('select', function(selected, option) {
    return (selected == option) ? 'selected="selected"' : '';
});

.hbs

<select name="status">
    <option value="public" {{{select story.status 'public'}}}>Public</option>
    <option value="private" {{{select story.status 'private'}}}>Private</option>
    <option value="unpublished" {{{select story.status 'unpublished'}}}>Unpublished</option>
</select>

Solution 8 - Javascript

I prefer to use a template approach. By this I mean the layout of the option tag itself is specified in the handlebars template (where someone might look for it) and not in the javascript helper. Template inside the block helper is passed into the helper script and can be used by calling options.fn() which then uses any script changes you have made in your helper.

Template:

<select>
  {{#optionsList aStatusList sCurrentStatusCode 'statusCode'}}
    <option {{isSelected}} value="{{statusCode}}">{{statusName}}</option>
  {{/optionsList}}
</select>

Slightly modified data (not required but a little more "real world" for me)

var myOrder = 
{
    "id"     : 1,
    "name"   : "World",
    "statusName" : "OverDue", /* status should NOT be here! */
    "statusCode" : "1",
    "date"   : "2012-12-21"
}

var sCurrentStatusCode = myOrder.statusCode;

var aStatusList =
[    {        "statusName"       : "Node",        "statusCode" : 0    },    {        "statusName"       : "Overdue",        "statusCode" : 1    },    {        "statusName"       : "Completed",        "statusCode" : 2    },    {        "statusName"       : "Sent to Payer",        "statusCode" : 3     }]

Javascript registered helper:

Handlebars.registerHelper( 'optionsList',
function ( aOptions, sSelectedOptionValue, sOptionProperty, options )
{
  var out = "";
  for ( var i = 0, l = aOptions.length; i < l; i++ )
  {
    aOptions[ i ].isSelected = '';
    if( ( sOptionProperty != null &&  sSelectedOptionValue == aOptions[ i ][ sOptionProperty ] ) || (  sSelectedOptionValue == aOptions[ i ] ) )
    {
      aOptions[ i ].isSelected = ' selected="selected" ';
    }
    out = out + options.fn( aOptions[ i ] );
  }
  return out;
} );

optionsList is what I have chosen to name this helper

aStatusList an array of status objects contain several properties including the status value/name (in most cases I have encountered this would be the status code not the status name that is stored )

sCurrentStatus is the previously selected status code (not the value) and is the option value that i would like to have the selected in this generated option list.

statusCode is the string property name within a aStatusList object that I will test to see if it matches myStatus that is aStutusList[ loopIndex ][statusCode]

  • the string option property ( statusCode in this case ) is only required for objects -- options lists may also be arrays of strings (instead of objects that in turn containing strings) in which case you may omit the the third property 'statusCode' which tells the helper what property of the object to test agains. If you don't pass that property it will just test againts the list item itself.
  • if the sSelectedOptionValue is not passed then the list will be produced without setting any item to selected. This will generate the list pretty much the same as using the {{#each}} helper

Solution 9 - Javascript

I just ran into this problem, here's a solution for when the options are dynamic..

Instead of creating a select helper, I created an option helper that accepts the value of the item you wish to be selected.

Handlebars.registerHelper('option', function(value) {
  var selected = value.toLowerCase() === (this.toString()).toLowerCase() ? 'selected="selected"' : '';
  return '<option value="' + this + '" ' + selected + '>' + this + '</option>';
});

And in my template.

{{#items}}
    {{{option ../selected_value}}}
{{/items}}

Please note the ../ to access the parent's scope as it's not likely the selected_value will be inside of the items array.

Cheers.

Solution 10 - Javascript

If you have very few options and you don't want to write a helper, here is what you can do:

//app.js
var data = {selectedVal: 'b'}; 

// can also use switch ... case ...
if (data.selectedVal === 'a') {
   data.optionASelected = true;
} else if (data.selectedVal === 'b') {
   data.optionBSelected = true;
}

// assuming you have a template function to handle this data
templateFunc(data);

In your template file:

<!-- template.html -->
<select id="select-wo-helper" >
  <option value="a" {{#if optionASelected}} selected {{/if}}>A</option>
  <option value="b" {{#if optionBSelected}} selected {{/if}}>B</option>
</select>

Again this may NOT be the best solution of all, but it probably is a very quick work around when you are dealing very few options and wanted a quick fix.

Solution 11 - Javascript

Today I was also facing the same problem I'm creating a Content Management System and to fetch the status of the post I stuck and in searching for a solution, I landed on this page I found a few answers relevant Because I'm using server-side data and when I used document.createElement it is throwing error document is not defined.

The Regex solution worked for me but I want an easy to understand one whether it is verbose so I came with this solution.

 Handlebars.registerHelper('getValue', function(value, options) {
   if(options.fn(this).indexOf(value) >= 1){
         return `selected='selected'`;
      }
});

in the template use the code in this way

  <select name="post-status" id="post-status">
        <option {{#getValue posts.postStatus}} value="Draft" {{/getValue}} >Draft</option>
        <option {{#getValue posts.postStatus}} value="private" {{/getValue}} >private</option>
        <option {{#getValue posts.postStatus}} value="publish" {{/getValue}} >publish</option>
</select>

If I'm wrong somewhere Please correct me.

Solution 12 - Javascript

Data source

selectedValue: "8",
option:[
	{id_sub_proyectop: "8", clave: "021", name: "Cliclismo"},
	{id_sub_proyectop: "9", clave: "022", name: "Atletismo"},
],

helper

Handlebars.registerHelper('selected', function(value, prop) {
    if (value === undefined){ return '';};
    return value === this[prop] ? 'selected="selected"' : '';
});

Template

<div class="medium-6 cell">
    <label>Sub Proyecto / Proceso:
        <select name="id_sub_proyectop" required>
            {{#each option}}
            <option value="{{id_sub_proyectop}}" {{{selected ../selectedValue 'id_sub_proyectop'}}}>{{clave}} - {{name}}</option>
            {{/each}}
        </select>
    </label>
</div>

Solution 13 - Javascript

It should be mentioned that if you do not care about repeats... you can just use vanilla handlebars and place the selected option first, such as:

        <select name="ingredient">
        <option value="{{ingredient.id}}" selected>{{ingredient.name}}</option>
        {{#each ingredients}}
        <option value="{{this.id}}">{{this.name}}</option>
        {{/each}}
        </select>

Solution 14 - Javascript

@lazd's answer does not work for <option> elements within an <optgroup>.

selectedIndex is numbered monotonically for all <option>s, but select.children holds the <optgroup>s, and select.children[n].children holds the <option>s within <optgroup> n (with numbering restarting within each <optgroup>, of course).

This alternative version will work for <option> elements within <optgroup>s:

Handlebars.registerHelper('select-optgrp', function(value, options) {
    var select = document.createElement('select'); // create a select element
    select.innerHTML = options.fn(this);           // populate it with the option HTML
    select.value = value;                          // set the value
    var g = 0, i = select.selectedIndex;           // calculate which index of which optgroup
    while (i >= select.children[g].children.length) { i -= select.children[g].children.length; g++; }
    if (select.children[g].children[i]) {          // if selected node exists add 'selected' attribute
        select.children[g].children[i].setAttribute('selected', true);
    }
    return select.innerHTML;
});

Solution 15 - Javascript

Another one solution using express-handlebars and dynamic options is this.

Helper function (From all options takes the one we want and change it to selected).

select: function(selected, options) {
    return options.fn(this)
      .replace( new RegExp(' value=\"' + selected + '\"'), '$& selected="selected"')
      .replace( new RegExp('>' + selected + '</option>'), ' selected="selected"$&');
  }

handlebars file (I just use #each inside select to receive me data and worked like a charm).

<select name="justAname">
  {{#select content.campusId}}
    {{#each campus}}
      <option value="{{id}}">{{name}}</option>
    {{/each}}
  {{/select}}
</select>

Solution 16 - Javascript

Handlebars.registerHelper('select', function( value, options ){
  return options.fn(this)
  .replace( new RegExp(' value=\"' + value + '\"'), '$& selected="selected"')
  .replace( new RegExp('>' + value + '</option>'), ' selected="selected"$&');
});

user.country from db session
country stored in country.json file

<select id="country" name="country" class="form-control">
<option value="" selected="selected">(please select a country)</option>
{{#select user.country}}
{{#each countries as |value key| }}
<option value="{{ value.code }}">{{ value.name }}</option>
{{/each}}
{{/select}}
</select>

Solution 17 - Javascript

I know this does not answer the question directly, but in this situation, I passed the unselected html options to the template, and after it is rendered, I use jquery to mark as selected the value that the json object indicates

Solution 18 - Javascript

for arrays

function select(selected, options) {
    return options.fn(this).replace( new RegExp(' value=\"' + selected + '\"'), '$& selected="selected"').replace( new RegExp('>' + selected + '</option>'), ' selected="selected"$&');
  },

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionu840903View Question on Stackoverflow
Solution 1 - Javascriptu840903View Answer on Stackoverflow
Solution 2 - JavascriptDanView Answer on Stackoverflow
Solution 3 - JavascriptlazdView Answer on Stackoverflow
Solution 4 - JavascriptztareView Answer on Stackoverflow
Solution 5 - JavascriptalancasagrandeView Answer on Stackoverflow
Solution 6 - JavascriptIsmailSView Answer on Stackoverflow
Solution 7 - JavascriptstillatmylinuxView Answer on Stackoverflow
Solution 8 - JavascriptQuincy AcklenView Answer on Stackoverflow
Solution 9 - JavascriptGerwinView Answer on Stackoverflow
Solution 10 - JavascriptbenjaminzView Answer on Stackoverflow
Solution 11 - JavascriptMD M NaumanView Answer on Stackoverflow
Solution 12 - JavascriptDaniel ReyesView Answer on Stackoverflow
Solution 13 - JavascriptRay FossView Answer on Stackoverflow
Solution 14 - JavascriptChrisVView Answer on Stackoverflow
Solution 15 - JavascriptyannisalexiouView Answer on Stackoverflow
Solution 16 - Javascriptdavid dasView Answer on Stackoverflow
Solution 17 - JavascriptbachsteinView Answer on Stackoverflow
Solution 18 - JavascriptVinceView Answer on Stackoverflow