You just need to make an array of unique items before your $.each()
. This can be done many different ways. Here is one way to do it.
Edit
(11-24-2019)
<option value="NZ">New Zealand</option> //New Zealand is selected option
No it's not. The markup does not show the selected
attribute on the option
. It might have appeared to be selected because it was the first entry in the list and no option
elements were selected.
Perhaps you want "New Zealand" to show up after "India". You either place the option
in that order, mark it selected
, and it will be displayed in the select
while appearing in the list after "India", OR you can allow nothing to be selected
and insert the "New Zealand" option
after "India", but it will not be displayed in the select
(just in the drop down list, the first option
would be displayed in the select (but not actually selected
)).
In most scenarios, one wants a particular sort order, with a default selection. The most common alternative being nothing selected
and adding a disabled
option
as the first entry in the list (with empty text or a placeholder, e.g. "Please select a country"). The latter is pretty straightforward, but I think the original question is best solved with the behavior of the former.
So, you need a way to tell $.each()
's callback function which option
to mark selected
when it is appending them. My original answer was the correct way to deal with the root of the problem (duplicate entries in the list). There is no need to clean them up if they are never added. Make a distinct list of countries, then call the $.each()
.
Here would be my new favorite way to make an array distinct:
arr = [...new Set(arr)]
So, rewriting this in 2019...
// If you want to specify a default selection, modify `toOption` as such...
//const defaultSelection = 'NZ';
//const toOption = c =>
// new Option(c.name, c.code, c.code === defaultSelection, c.code === selected);
const toOption = c =>
new Option(c.name, c.code, false, c.code === selected);
const country= document.querySelector('#country');
// If you wish to retain previously selected country...
//const idx = country.selectedIndex;
//const selected = idx > -1 ? country.options[idx].value : null;
const selected = 'NZ';
while (country.firstChild) country.firstChild.remove();
const resp = await fetch('/url/allcountries');
[...new Set((await resp.json()).items)]
.map(toOption).forEach(country.appendChild);
I would probably use the two commented out blocks. In conjunction they would allow for the previous selection to always be retained, while defaulting to the selection of your choice when there would be nothing selected.
Notice the .items
appended to .json()
. These examples assume your api response is of type application/json
, and has an array, items
directly under the root:
{
"items": [{
"code": "USA",
"name": "United States"
}, {
"code": "Ind",
"name": "India"
}, {
"code": "NZ",
"name": "New Zealand"
}, {
"code": "SA",
"name": "South Africa"
}, {
"code": "UK",
"name": "United Kingdom"
}, {
"code": "JP",
"name": "Japan"
}]
}
This also assumes your response object will have an items
property that is an Array
, even when empty ({ "items": [] }
). I think this is a safe assumption because this is a non-CORS request, meaning you can very likely control the server, so you can make sure you are returning an empty list, and not null
, false
, etc. Otherwise, you will want to save the response JSON to a variable, and add the if
statement back in...
const resp = await fetch(...)
const json = await resp.json();
if (json && json.items /*&& json.items.length */)
json.items.map(...).forEach(...);
...with optional chaining...
json?.items?.map(...).forEach(...);
Lastly, you might have a selected option coming back from the server.
{
"items": [{
"code": "USA",
"name": "United States",
"isDefault": true <---default
}, {
"code": "Ind",
"name": "India"
}, {
"code": "NZ",
"name": "New Zealand",
"selected": true <---currently selected
}, {
"code": "SA",
"name": "South Africa"
}, {
"code": "UK",
"name": "United Kingdom"
}, {
"code": "JP",
"name": "Japan"
}]
}
In that case one of the items would have some flag indicating such, and the instantiation of Option
s would become...
new Option(c.name, c.code, c.code === defaultSelection, c.selected)
You may want to consider multiple items with selected: !falsey
. By default, the last item matching this criteria will be the resulting selected option.
I also added a hypothetical isDefault
. It would allow for the server to set the default selection.
new Option(c.name, c.code, c.isDefault, c.selected)
The resulting meat and potatoes (which will currently only work in Chrome [maybe Opera] without polyfills/babel/etc.):
async function updateCountries() {
const country= document.querySelector('#country');
while (country.firstChild) country.firstChild.remove();
const resp = await fetch('/url/allcountries');
const json = await resp.json();
[...new Set(json?.items)]
.map(c => new Option(c.name, c.code, c.isDefault, c.selected))
.forEach(country.appendChild);
}
Disclaimer: absolutely none of this code has been tested in all major browsers, as well as all sergeant and corporal browsers