Search code examples
javascriptregexhandlebars.js

Looped RegEx creating second set of elements


I have a handlebars helper that populates select boxes, and I've modified it so it will work with multiple choice select boxes. While it now successfully checks mutiple boxes, it now makes the drop down have two sets of each options.

select: function(selected, options){
  let selLength;
  let newOptions;
  if(Array.isArray(selected) == true){
    selLength = selected.length;
    let i;
    for(i = 0; i < selLength; i++){
      newOptions += options.fn(this).replace(new RegExp(' value=\"' + selected[i] + '\"'), '$& selected="selected"').replace(new RegExp('>' + selected[i] + '</option>'), ' selected="selected"$&');
    } return newOptions
   } else {
return options.fn(this).replace(new RegExp(' value=\"' + selected + '\"'), '$& selected="selected"').replace(new RegExp('>' + selected + '</option>'), ' selected="selected"$&');
  }
},

This is being called via:

    {{#select userStatus.userGroup}}
      <option value="" disabled selected>Choose your option</option>
      <option value="IT">IT / Development</option>
      <option value="Management">Management</option>
      <option value="Analyst">Analyst</option>
      <option value="orderFul">Order Fulffilment</option>
      <option value="QA">Quality Analyst</option>
      <option value="Rep">Company Rep</option>
    {{/select}}

userGroup is an array containing the groups that the user belongs to.


Solution

  • I do understand what you are trying to do. I have experience with this sort of thing and if you had taken the time to understand what I was advising I think you would have found it helpful.

    Your problem is not with the regular expressions. As I noted in my comment, you are appending to your output your entire #select block template for each item in userStatus.userGroup`.

    To clarify: Where in your helper you have options.fn(this), you can think of this as a string containing your template with all of your <option> elements. Each time you do newOptions += options.fn(this) you are just appending that block to your output; and you are doing this once for each element in selected which is userStatus.userGroup. So if you have 3 elements in userStatus.userGroup, you will output your options 3 times:

    To fix your helper, you need to stop appending to your output and instead perform all of the replacing on a single string instance.

    let selLength = selected.length;
    let newOptions = options.fn(this);
    let i;
    
    for (i = 0; i < selLength; i++) {
        newOptions = newOptions.replace(new RegExp(' value=\"' + selected[i] + '\"'), '$& selected="selected"').replace(new RegExp('>' + selected[i] + '</option>'), ' selected="selected"$&');
    }
    
    return newOptions;
    

    See this fiddle for reference.

    This code will fix your helper, but it is a terrible way to write your application.

    The purpose of a template is to produce a string output via data interpolation and conditional control structures. It does not make sense to create a helper that takes your template's output and then performs replacements on that output. The logic of those replacements should be inside of your template.

    I think a better approach would begin by separating your data from your template. I would advise making the options into data that you supply to your template. For example:

    options: [
        {
            label: 'IT / Development',
            value: 'IT'
        },
        {
            label: 'Management',
            value: 'Management'
        },
        {
            label: 'Analyst',
            value: 'Analyst'
        },
        {
            label: 'Order Fulfillment',
            value: 'orderFul'
        },
        {
            label: 'Quality Analyst',
            value: 'QA'
        },
        {
            label: 'Company Rep',
            value: 'Rep'
        }
    ]
    

    And your template will become the much simpler and less noisy:

    {{#each options}}
        <option value="{{value}}">{{label}}</option>
    {{/each}}
    

    Next, we must figure out how to solve the problem of adding the selected attribute to each option whose value can be found in userStatus.userGroup. For this we can write a simple helper that will tell us if some value is found in an Array. This use of a helper is legitimate because it is not manipulating our output, but only helping with the conditional logic of our template. Here is a simple example of such a helper:

    Handlebars.registerHelper('contains', function (array, value) {
        if (!Array.isArray(array)) { return false; }
        return array.indexOf(value) > -1;
    });
    

    Now we can use this helper in our template to determine which <option> elements should have the selected attribute:

    {{#each options}}
        <option {{#if (contains ../userStatus.userGroup value)}}selected{{/if}} value="{{value}}">{{label}}</option>
    {{/each}}
    

    See this fiddle for reference.

    I hope you can appreciate why this approach is preferable.