1

I have a dropdown list having duplicate entries. One of the duplicate entry is the selected value in the dropdown. Eg:

<select id="country">
  <option value="NZ">New Zealand</option> //New Zealand is selected option
  <option value="USA">United States</option>
  <option value="Ind">India</option>
  <option value="NZ">New Zealand</option>
  <option value="SA">South Africa</option>
  <option value="UK">United Kingdom</option>
 <option value="JP">Japan</option>
</select>

Now, im trying to remove the New Zealand option (duplicate), but at the same time, im trying to make the other entry of the New Zealand selected, so that I see the list as:

<select id="country">

  <option value="USA">United States</option>
  <option value="Ind">India</option>
  <option value="NZ" selected>New Zealand</option> //removing the duplicate selection.
  <option value="SA">South Africa</option>
  <option value="UK">United Kingdom</option>
 <option value="JP">Japan</option>
</select>

here is the JavaScript used to populate the dropdown:

$.ajax('/url/allcountries', {
    method:'GET',
    success: function(items){               
        if(items){
          var items = items;
           $.each(items, function(i, item) {
             $("#country").append($('<option></option>').val(item.code).html(item.name));
            //Eg: where item.code = "USA" and item.name ="United States"

            if($("#country :selected").text() === item.code){
                $("#country :selected").html(item.name);
                //Edit as per suggested
                var x = {};
                $("select[id='country'] > option").each(function () {
                 if(x[this.text]) {
                    $(this).remove();
                 } else {
                  x[this.text] = this.value;
                }
            });
               //this gives me a duplicate entry at the top of the list.
             }
          });
      } //if condition ends
   }//success ends
});

Any ideas on how to remove duplicates and make only of them selected?

user1234
  • 3,000
  • 4
  • 50
  • 102

3 Answers3

1

You can try like this:

[].slice.call(country.options)
  .map(function(a){
    if(this[a.innerText]){ 
      country.removeChild(a); 
    } else { 
      this[a.innerText]=1; 
    } 
  },{});

JSFIDDLE DEMO

or in Jquery like this:

var x = {};
$("select[name='country'] > option").each(function () {
    if(x[this.text]) {
        $(this).remove();
    } else {
        x[this.text] = this.value;
    }
});
Rahul Tripathi
  • 168,305
  • 31
  • 280
  • 331
  • didnt help me...i tried the way u suggested shown above...still teh same for me..I'm a placing it wrong? – user1234 Apr 10 '15 at 20:10
  • @user1234 his answer is intended to clean up the list, removing duplicate options *after they're created and appended to the DOM*. You would run to run that, maybe using a timeout, after your .each() – Eric Lease Apr 10 '15 at 20:50
0

before you append the option element.check if there's another element with the same value if so check it and move to the next option.

success: function(items){               
        if(items){
          var items = items;
           $.each(items, function(i, item) {
             if($('option[value='+item.code+']').length!==0){
             $('option[value='+item.code+']')[0].selected='true';
             return 0;
             }
             $("#country").append($('<option></option>').val(item.code).html(item.name));
            //Eg: where item.code = "USA" and item.name ="United States"

            if($("#country :selected").text() === item.code){
                $("#country :selected").html(item.name);
                //Edit as per suggested
                var x = {};
                $("select[id='country'] > option").each(function () {
                 if(x[this.text]) {
                    $(this).remove();
                 } else {
                  x[this.text] = this.value;
                }
            });
               //this gives me a duplicate entry at the top of the list.
             }
          });
      } //if condition ends
   }//success ends
Omar Elawady
  • 3,300
  • 1
  • 13
  • 17
-1

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 Options 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

Community
  • 1
  • 1
Eric Lease
  • 4,114
  • 1
  • 29
  • 45