3

I have a HTML select dropdown with multiple provided:

<select multiple id="mySelect">
  <option>Apple</option>
  <option>Orange</option>
  <option>Pineapple</option>
  <option>Banana</option>
</select>

and assign a click handler which returns the currently clicked on elements' index:

document.getElementById("mySelect").onclick = function() {
  alert(this.selectedIndex);
}

Which works fine when I select one of the elements only. But I want to return all indexes currently selected.

When I click Apple, Orange and Banana a return like [0,1,3] is what I want, but it does not work.

Working example: JSfiddle

supersize
  • 13,764
  • 18
  • 74
  • 133

5 Answers5

5

There is a browser API for that selectedOptions but IE is not supported.

document.getElementById("mySelect").onclick = function() {
  console.log(Array.from(this.selectedOptions).map(option => option.index))
}
<select multiple id="mySelect">
  <option>Apple</option>
  <option>Orange</option>
  <option>Pineapple</option>
  <option>Banana</option>
</select>
supersize
  • 13,764
  • 18
  • 74
  • 133
TheWandererLee
  • 1,012
  • 5
  • 14
  • great this works and is a browser API. Can you amend your answer to reflect my exact question. Just remove the extra `button` and do: `Array.from(this.selectedOptions).map(v=>v.index)` because your example returns the values not the index. – supersize Jun 17 '19 at 13:45
  • I quote: "*When I click `Apple`, `Orange` and `Banana` a return like `[0,1,3]` is what I want*", whereas your code logs `["Apple", "Orange", "Banana"]`. You also use obtrusive JavaScript, binding your event-handlers in the HTML and restrict the function to working with only the one element; if this solution is used - and I agree, it's a valid approach to a solution - then at least warn OP, and future users, of the problems and restrictions, particularly the maintenance issues. – David Thomas Jun 17 '19 at 14:01
  • `v.index` returns the index though. The answer is a bit badly written, @Daryll check my comment above. Also it would be good if you could add the browser API article (https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/selectedOptions) to your answer. – supersize Jun 17 '19 at 14:04
  • 1
    @Daryll did it for you – supersize Jun 17 '19 at 14:22
  • While I disagree with the use of inline event-handlers, as well as the other problems raised previously, I'm up-voting for the use, and reminder, of `HTMLSelectElement.selectedOptions`. I'm embarrassed to have forgotten about that collection. – David Thomas Jun 17 '19 at 14:45
  • @supersize Thank you for the edit. I had tried unsuccessfully with .keys(). .index does work as expected. – TheWandererLee Jun 17 '19 at 15:00
2

One option:

// named function for reuse, not using Arrow syntax in
// order that we can retain, and use the 'this' within
// the function:
const retrieveSelectedIndices = function() {
  // here we use the spread operator to
  // expand the iterable this.options collection,
  // allowing us to call Array.prototype.map()
  // (Array.from(this.options) would be more or less
  // identical:
  const selected = [...this.options].map(
    // we're not using 'this', so using Arrow
    // function expression; here we use a ternary
    // to return the index of the current <option>
    // if it is selected, or Boolean false if not:
    (opt, index) => opt.selected ? index : false

    // we then call Array.prototype.filter() on
    // the Array created by Array.prototype.map(),
    // and here we test that i - the index retrieved
    // previously - is not equal to false:
  ).filter(((i) => i !== false));

  // we log the indices to the console(), but this is
  // where you could work with the indices, for logging
  // to an HTML <ul></ul> element, for example:
  console.log(selected);

  // and returning to the calling context, in the event
  // this function will be used in an alternative manner:
  return selected;
}

// using document.querySelector() to retrieve the first,
// and only, element matching the supplied CSS selector:
selectEl = document.querySelector('#mySelect');

// binding a function, the retrieveSelectedIndices()
// function, as the 'input' event-handler for on
// the <select id="mySelect"> element:
selectEl.addEventListener('input', retrieveSelectedIndices);
<select multiple id="mySelect">
  <option>Apple</option>
  <option>Orange</option>
  <option>Pineapple</option>
  <option>Banana</option>
</select>

Please note that the above function does not maintain the order of selected/deselected elements.

Edited to use HTMLSelectElement.selectedOptions in place of the HTMLSelectElement.options, the existence of which I'd forgotten until the discussion in the comments. This reflects an update, rather than any attempt to gain credit for Daryll's sensible use of HTMLSelectElement.selectedOptions.

That said, then, the above code could easily be modified to the following:

// named function for reuse, not using Arrow syntax in
// order that we can retain, and use the 'this' within
// the function:
const retrieveSelectedIndices = function() {
  // here we use the spread operator to
  // expand the iterable this.selectedOptions collection,
  // a HTMLCollection of the currently-selected <option>
  // elements, allowing us to call Array.prototype.map()
  // (Array.from(this.selectedOptions) would be more or less
  // identical:
  const selected = [...this.selectedOptions].map(
    // we're not using 'this', so using Arrow
    // function expression; here we use a ternary
    // to return the index of the current <option>
    // if it is selected, or Boolean false if not:
    (opt) => opt.index
  )

  // we log the indices to the console(), but this is
  // where you could work with the indices, for logging
  // to an HTML <ul></ul> element, for example:
  console.log(selected);

  // and returning to the calling context, in the event
  // this function will be used in an alternative manner:
  return selected;
}

// using document.querySelector() to retrieve the first,
// and only, element matching the supplied CSS selector:
selectEl = document.querySelector('#mySelect');

// binding a function, the retrieveSelectedIndices()
// function, as the 'input' event-handler for on
// the <select id="mySelect"> element:
selectEl.addEventListener('input', retrieveSelectedIndices);
<select multiple id="mySelect">
  <option>Apple</option>
  <option>Orange</option>
  <option>Pineapple</option>
  <option>Banana</option>
</select>

References:

David Thomas
  • 249,100
  • 51
  • 377
  • 410
  • thanks, actually an answer on here found out there is a browser api for that: https://stackoverflow.com/a/56631915/5514077 which is I think even better. – supersize Jun 17 '19 at 13:47
  • Except that solution retrieves the values, not the indices (which is not what you want, according to your question), it can only be used on one element (since the targeted element is hardcoded), using obtrusive JavaScript and an inline event-handler (`onclick` as opposed to applying an event-listener). My approach is more extensible, doesn't 'pollute' the HTML with JavaScript (which ensures easier future maintenance) and performs the task that you *asked* for (finding the indices not the values). So by all means, if you prefer it then use it (it's bad, but valid). But be aware of the problems. – David Thomas Jun 17 '19 at 13:58
  • I see your point but I have lead him to correct his answer a bit. Checking the object returned from `selectedOptions` it has indeed an `index` property which is corresponding to the index of the element. So when he amends the answer with the correct property to be accessed (hence `v.index`) it is exactly whats needed built in in the browser API, no extra function needed. Downside is though that IE is not supported currently. – supersize Jun 17 '19 at 14:01
  • You're aware that every answer here utilises the DOM API? The function written in that answer no more, or less, than any other. It's simply less useful due to the restrictions I mention above. But, again, if you're happy with it then that's your call. Incidentally if you plan to use that answer you may wish to accept that answer (but, obviously, I'd suggest highlighting the fact that you're potentially accepting an answer that doesn't satisfy the question you asked). – David Thomas Jun 17 '19 at 14:05
  • I'm waiting for his answer to be updated then I will switch the answer. But one thing that intrigues me, wouldn't you say that a method shipped with the browser should take precedence over one that you'll have to add even though there is an API for that already? – supersize Jun 17 '19 at 14:07
  • "*a method shipped with the browser*" - what do you think shipped with the browser in that answer, that doesn't in my own? – David Thomas Jun 17 '19 at 14:09
  • Technically that's correct. What I mean by method shipped is that `selectedOptions` is already implemented while looping over selected options is something I have to add a method for (like your example). I might be wrong here but in my head it feels like we're saving a step (filtering) – supersize Jun 17 '19 at 14:18
  • Ah. Sorry, I - for some reason - overlooked the use of `HTMLSelectElement.selectedOptions`, and mistook it for `HTMLSelectElement.options`; in which case, yes: that property does make more sense. I have updated my own answer to use that property, in a second code block; that's to improve my answer but I'm not trying to take credit for the use of the preferred property/collection. – David Thomas Jun 17 '19 at 14:43
  • No worries, could have very well been that I'd have overlooked something. Thanks for this in detail answer. – supersize Jun 17 '19 at 14:57
0

Try like this.

I have looped the options and checked for selected options and pushed it. and finally I joined the array and printed it.

var selectEl = document.querySelector('#mySelect');
var options = document.querySelectorAll('#mySelect option');
selectEl.addEventListener('click', function() {
var arr=[]
    options.forEach(function(option, index) {
      if(option.selected) { arr.push(index); }
    })
console.log(arr.join(","));
})
<select multiple id="mySelect">
  <option>Apple</option>
  <option>Orange</option>
  <option>Pineapple</option>
  <option>Banana</option>
</select>
Bash
  • 628
  • 6
  • 19
Syed mohamed aladeen
  • 6,507
  • 4
  • 32
  • 59
0

Get all the options. Add change event to your select, get selected options, iterate over it to find the index of individual selected option.

const options = document.querySelectorAll('#mySelect > option');

let indexes = [];
document.querySelector('#mySelect').addEventListener('change', function() {
    indexes = [];
    const selectedOptions = this.selectedOptions;
    [...selectedOptions].forEach((option) => {
        const index = [...options].indexOf(option);
        indexes.push(index) ;
    })
    console.log(indexes);
});
<select multiple id="mySelect">
  <option>Apple</option>
  <option>Orange</option>
  <option>Pineapple</option>
  <option>Banana</option>
</select>
random
  • 7,756
  • 3
  • 19
  • 25
  • This looks about right, still it doesn't solve the mystery about my initial question why the API isn't implemented like this by default. Thanks – supersize Jun 17 '19 at 12:15
  • Unless a less verbose version comes out I accept :) – supersize Jun 17 '19 at 12:17
  • @supersize It is supported by default. See [HTMLSelectElement.selectedOptions](https://developer.mozilla.org/en-US/docs/Web/API/HTMLSelectElement/selectedOptions). See [my new answer](https://stackoverflow.com/a/56631915/5514077) for the one-liner demo. – TheWandererLee Jun 17 '19 at 13:04
  • @Daryll can you do an answer because this should be clearly accepted then. – supersize Jun 17 '19 at 13:08
0
allTheOptions.forEach(function(items,i) {
    if (items.selected) {
      answer.push(i);
    }
  })
};

If the item is selected that push the index into array. Simple as that.

let allTheOptions = document.querySelectorAll('#mySelect option');
let select = document.querySelector('#mySelect');
select.addEventListener('change', function() {
  let answer = [];
  allTheOptions.forEach(function(items,i) {
    if (items.selected) {
      answer.push(i);
    }
  })
  console.log(answer);
})
<select multiple id="mySelect">
  <option>Apple</option>
  <option>Orange</option>
  <option>Pineapple</option>
  <option>Banana</option>
</select>
weegee
  • 3,256
  • 2
  • 18
  • 32