7

I apologize if my terminology is incorrect — this is definitely not my area of expertise.

I am looking to make a <select> list from a json file, and group the entries in <optgroup>s by a key. I have successfully been able to list all the entries in a select, but don't know how to loop through and nest items under their key.

My JSON looks like this:

[
    {
        "Type" : "Overdrive",
        "Brand" : "Chase Bliss",
        "Name" : "Brothers",
        "Width" : 2.75,
        "Height" : 4.77,
        "Image" : "public/images/pedals/chasebliss-brothers.png"
    }
]

Here is how I am rendering the <select>:

window.RenderPedals = function(pedals){
    for(var i in pedals) {
        var $pedal = $("<option>"+ pedals[i].Brand + " " + pedals[i].Name +"</option>").attr('id', pedals[i].Name.toLowerCase().replace(/\s+/g, "-").replace(/'/g, ''));
        $pedal.data('width', pedals[i].Width);
        $pedal.data('height', pedals[i].Height);
        $pedal.data('height', pedals[i].Height);
        $pedal.data('image', pedals[i].Image);
        $pedal.appendTo('.pedal-list');
    }
}

What I am looking for is this markup:

<select>
    <optgroup label="Chase Bliss">
        <option data-width="..." data-height="..." data-image="...">Chase Bliss Brothers</option>
    </optgroup>
</select>

Can anyone point me in the right direction?

Travis L
  • 683
  • 2
  • 7
  • 12
  • 1
    If `pedals` is an array, you should take a look at [this question and its answers](http://stackoverflow.com/questions/500504/why-is-using-for-in-with-array-iteration-a-bad-idea?rq=1). – Heretic Monkey Mar 01 '17 at 18:35

2 Answers2

0

You can get properties of current object using object destructuring, without for..in loop, create an <optgroup> element with label attribute set to "Brand", append <option> to <optgroup>, append <optgroup> to <select>. Check if an <optgroup> element having label attribute set to Brand exists, if true, append the <option> to that <optgroup>, else append a new <optgroup> element to <select> with html set to new <option> element.

var data = [{
  "Type": "Overdrive",
  "Brand": "Chase Bliss",
  "Name": "Brothers",
  "Width": 2.75,
  "Height": 4.77,
  "Image": "public/images/pedals/chasebliss-brothers.png"
}
, {
  "Type": "Underdrive",
  "Brand": "Bliss Chase",
  "Name": "Sisters",
  "Width": 5.72,
  "Height": 7.74,
  "Image": "public/images/pedals/chasebliss-sisters.png"
 }
 , {
  "Type": "Throughdrive",
  "Brand": "Bliss Chase",
  "Name": "Cousins",
  "Width": 2.75,
  "Height": 4.74,
  "Image": "public/images/pedals/chasebliss-cousins.png"
 }
];

window.RenderPedals = function(pedals) {
  var {Type, Brand, Name, Width, Height, Image} = pedals;
  var option = $("<option>", {
                 text: `${Brand} ${Name}`,
                 id: Name.toLowerCase()
                     .replace(/(\s+)|(['"])/g, (m, p1, p2) => p1 ? "-" : ""),
                 data: {
                   width: Width,
                   height: Height,
                   image: Image
                 }
               });
  if ($("optgroup").is(`[label="${Brand}"]`)) {
    $(`optgroup[label="${Brand}"]`).append(option);
  } else {
    $("<optgroup>", {
      label: Brand,
      html: option
    }).appendTo(".pedal-list");
  }
}

data.forEach(RenderPedals);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js">
</script>
<select class="pedal-list"></select>
guest271314
  • 1
  • 15
  • 104
  • 177
  • Won't this miss the 'grouping' part? i.e. All entries in the array will be listed under the first Brand. – bsyk Mar 01 '17 at 18:53
  • @bsyk Not sure what you mean? Only one object appears within array at Question. OP could use `data.forEach(RenderPedals)` – guest271314 Mar 01 '17 at 19:01
  • I assumed that the data in the question was simplified to a single entry and that in a real application that there would be more entries, otherwise what's the point of a grouped listbox. Try adding more entries to your array with different Brand values. – bsyk Mar 01 '17 at 19:03
  • This does exactly what I need! Thank you – Travis L Mar 01 '17 at 20:10
  • Probably a noob question but my compiler doesn't seem to want to compile this syntax. Is there another way to write this that doesn't use backwards ticks? – Travis L Mar 01 '17 at 20:25
  • @TravisL Yes. You can replace template literals with string concatenation. Most importantly the attribute selector value at `if..else` should be quoted as there are, or could be, space characters at value of `label` value. Cannot re-write presently due to prior obligation. – guest271314 Mar 01 '17 at 20:27
  • 1
    @guest271314 Figured out which gulp plugin was hanging up — works perfectly now. Thanks again! – Travis L Mar 01 '17 at 20:52
0

If you had a data structure that matched the HTML, it would be much easier to reason about.

given

[
    {
        "Type" : "Overdrive",
        "Brand" : "Chase Bliss",
        "Name" : "Brothers",
        "Width" : 2.75,
        "Height" : 4.77,
        "Image" : "public/images/pedals/chasebliss-brothers.png"
    }
]

while you want the structure

<select>
    <optgroup label="Chase Bliss">
        <option data-width="..." data-height="..." data-image="...">Chase Bliss Brothers</option>
    </optgroup>
</select>

Instead of trying to make this transformation from JSON into the HTML, create a new data structure that matches the HTML. If data structure would match the HTML structure, the logic to render it would be much easier.

Something like the following would work well..

[
  {
    brandName: "Chase Bliss",
    items: [ { name: "Brothers" /* width, height, etc */ } ]
  }
]

You could write a reduce function to make this transformation:

let optionGroups = pedals.reduce((optionGroups, pedal) => { 
  let optionGroup = optionGroups.find(group => pedal.Brand === group.brandName) || {}

  if (!optionGroup.brandName) {
    optionGroup.brandName = pedal.Brand
    optionGroup.items = []
    optionGroups.push(optionGroup)
  }

  optionGroup.items.push(pedal)
  return optionGroups
}, [])

the render function could then just map over the outer groups, and then the inner items.

window.renderPedals = (optionGroups) => {
  optionGroups.map((group) => {
    let $optgroup = $("<optgroup>", {
      label: group.brandName
    });

    group.items.map((pedal) => {
      let {Type, Brand, Name, Width, Height, Image} = pedal;
      let $pedal = $("<option>", {
        text: `${Brand} ${Name}`,
        id: `${Name.toLowerCase().replace(/\s+/g, "-").replace(/'/g, '')}`,
        data: {
          width: Width,
          height: Height,
          image: Image
        },
        appendTo: $optgroup
      });
    })
    $optgroup.appendTo(".pedal-list");
  })
}

https://jsfiddle.net/f4x9vk0f/2/

Patrick Lee Scott
  • 8,217
  • 3
  • 36
  • 42