u840903
u840903

Reputation: 2171

How to set selected select option in Handlebars template

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?

Upvotes: 56

Views: 61182

Answers (18)

Vince
Vince

Reputation: 869

for arrays

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

Upvotes: 0

bachstein
bachstein

Reputation: 23

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

Upvotes: 0

Daniel Reyes
Daniel Reyes

Reputation: 297

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>

Upvotes: 1

MD M Nauman
MD M Nauman

Reputation: 431

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.

Upvotes: 1

david das
david das

Reputation: 1

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>

Upvotes: 0

yannisalexiou
yannisalexiou

Reputation: 707

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>

Upvotes: 0

stillatmylinux
stillatmylinux

Reputation: 1429

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>

Upvotes: 4

ztare
ztare

Reputation: 171

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>

Upvotes: 17

ChrisV
ChrisV

Reputation: 9091

@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;
});

Upvotes: 0

Ray Foss
Ray Foss

Reputation: 3883

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>

Upvotes: 0

benjaminz
benjaminz

Reputation: 3228

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.

Upvotes: 2

Quincy Acklen
Quincy Acklen

Reputation: 33

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

Upvotes: 3

u840903
u840903

Reputation: 2171

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!

Upvotes: 95

alancasagrande
alancasagrande

Reputation: 444

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>

Upvotes: 11

IsmailS
IsmailS

Reputation: 10863

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>

Upvotes: 7

Dan
Dan

Reputation: 1333

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.

Upvotes: 40

Gerwin
Gerwin

Reputation: 31

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.

Upvotes: 2

lazd
lazd

Reputation: 4687

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>

Upvotes: 20

Related Questions