5
\$\begingroup\$

Here is code I wrote to dynamically choose between three country options (United States, Canada, Other). After the user selects a country it will show the related select option or an empty input (when "Other" country is chosen). Then I pass the previous value into an input called spr(state/province/region). The end result is two values are saved: country and spr.

I'm here because I think the code could be refactored, and I'm not thinking about it in an efficient way.

const country = '#country';
const province = '#province';
const region = '#region';
const state = '#state';
const spr = '#spr';
const usa = '#usa';
const canada = '#canada';
const otherCountry = '#otherCountry';

hideCountryOptions(); //initial hide

$(country).on('change', function() {
  hideCountryOptions();
  selectSPR($(country).val());
  switch ($(country).val()) {
    case "UNITED STATES":
      $(usa).show();
      break;
    case "CANADA":
      $(canada).show();
      break;
    default:
      $(otherCountry).show();
  }
})

$(state + "," + province + "," + region).on('change', function() {
  selectSPR($(country).val());
});

function hideCountryOptions() {
  $(canada).hide();
  $(usa).hide();
  $(otherCountry).hide();
}

function selectSPR(country) { //pass value into the state/province/region input
  $(spr).val("");
  switch (country) {
    case "UNITED STATES":
      $(spr).val($(state).val());
      break;
    case "CANADA":
      $(spr).val($(province).val());
      break;
    default:
      $(spr).val($(region).val());
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
  <label for="country">*Country</label>
  <select name="country" id="country" required>
      <option value="">CHOOSE…</option>
      <option value="UNITED STATES">UNITED STATES</option>
      <option value="CANADA">CANADA</option>
      <option value="AFGHANISTAN">AFGHANISTAN</option>
    </select>
</div>
<div id="usa">
  <label for="state">*State</label>
  <select name="state" id="state">
      <option value="">CHOOSE...</option>
      <option value="ALABAMA">ALABAMA</option>
      <option value="ALASKA">ALASKA</option>
    </select>
</div>
<div id="canada">
  <label for="province">*Province</label>
  <select class="custom-select" name="province" id="province">
      <option value="">CHOOSE...</option>
      <option value="ALBERTA">ALBERTA</option>
      <option value="BRITISH COLUMBIA">BRITISH COLUMBIA</option>
    </select>
</div>
<div id="otherCountry">
  <label for="region">*Region</label>
  <input type="text" name="region" id="region" placeholder="Region">
</div>
<div>
  <input type="text" name="spr" id="spr">
</div>

\$\endgroup\$

3 Answers 3

4
\$\begingroup\$

There are 4 different places in the code, where you do something for every country. That makes it complicated to add more countries, and also makes the code very long if you have many. But you can get around this by designing your HTML for the task.


First of all, I've changed some names, and used the generic term 'region' everywhere. 'states' and 'provinces' are handled the same in the code, so a common name makes it easier. It also avoid a potential clash of IDs when adding more countries.

I've given each list of regions a common class to group them together, and a data-* attribute to match them with their country. I decided to use the 3-letter country codes, since we only need a unique identifier for each country, not the full display name.(1)

I use a class to indicate which region is active, and use CSS to hide the rest.

I've removed most name attributes, since they are not being used. Unless you are submitting this in a form, in which case I've left two, the 'country' and 'region', which are the two you wanted to save.

const country = $('#country');
const regions = $('.region');
const regionOther = $('.region[data-country=OTHER]');
const selectedRegion = $('#selectedRegion');

country.on('change', function() {
  let activeRegion = $(`.region[data-country=${country.val()}]`);
  if(!activeRegion.length) {
  	activeRegion = regionOther;
  }
  
  regions.removeClass('active');
  activeRegion.addClass('active');
  
  updateSelectedRegion();
})

regions.on('change', updateSelectedRegion);

function updateSelectedRegion() {
  let activeRegion = $('.region.active select, .region.active input');
  selectedRegion.val(activeRegion.val());
}
.region:not(.active) {
  display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
  <label for="country">*Country</label>
  <select id="country" name="country" required>
    <option value="">CHOOSE…</option>
    <option value="USA">UNITED STATES</option>
    <option value="CAN">CANADA</option>
    <option value="AFG">AFGHANISTAN</option>
  </select>
</div>

<div class="region" data-country="USA">
  <label for="regionUSA">*State</label>
  <select id="regionUSA">
    <option value="">CHOOSE...</option>
    <option value="ALABAMA">ALABAMA</option>
    <option value="ALASKA">ALASKA</option>
  </select>
</div>
<div class="region" data-country="CAN">
  <label for="regionCAN">*Province</label>
  <select id="regionCAN">
    <option value="">CHOOSE...</option>
    <option value="ALBERTA">ALBERTA</option>
    <option value="BRITISH COLUMBIA">BRITISH COLUMBIA</option>
  </select>
</div>
<div class="region" data-country="OTHER">
  <label for="regionOTHER">*Region</label>
  <input type="text" id="regionOTHER" placeholder="Region">
</div>

<div>
  <input type="text" id="selectedRegion" name="region" readonly>
</div>

Now if you want to add another country, all you have to change in the code is... nothing. You don't need to touch the code at all, just add the HTML. Just add another option

<option value="AUS">AUSTRALIA</option>

If the country doesn't have regions, that's it, the default will be used. If it does have, add this

<div class="region" data-country="AUS">
  <label for="regionAUS">*State</label>
  <select id="regionAUS">
    <option value="">CHOOSE...</option>
    <option value="NEW SOUTH WALES">NEW SOUTH WALES</option>
    <option value="QUEENSLAND">QUEENSLAND</option>
  </select>
</div>

Here's a full example with a few more countries http://jsfiddle.net/7fnwohb3/3/

(1) You can use short codes for the individual regions too. Most countries should have official region codes in 2-3 letters. This list have links to them. If you want to get the name from the code, have them in a list you can use everywhere in your code, like below. You could potentially use this list to generate the HTML options.

const regionNamesUSA = {
  AL: 'Alabama',
  AK: 'Alaska',
  AZ: 'Arizona',
  ...
}
\$\endgroup\$
1
  • \$\begingroup\$ Thanks, I like this answer for removing the need for a switch. \$\endgroup\$
    – Pslice
    Commented Aug 10, 2018 at 15:43
2
\$\begingroup\$

I reduced the script in this way:

  1. The script length (in lines): was 49, now 27
  2. The jQuery lookups: was 18, now 7
  3. The sub-function amount: was 2, now 0

There is still 2 change handlers.

The main changes:

I used classes... You were using any, except for custom-select that was not used here, in fact. And I used CSS for the onload hiding of countries sub-division selects.

The lookups are made onload, in the const declarations, for all needed elements. Why only store the selector's string when the jQuery object can be stored?

You had the named function hideCountryOptions() to call 3 times the .hide() method on "similar" elements... I gave them the same countryPossible class to hide them in one shot.

The selectSPR(country) named function was replaced by spr.val(this.value); on change of countrySubDiv, which is the lookup made to target all the country sub-division's user inputs. No need for a switch()! Clearly, only the select that is visible can change, so this.value is the one to put into #spr.

const country = $("#country");
const countryPossibilities = $(".countryPossible");
const countrySubDiv = $(".countryPossible select, .countryPossible input");
const usa = $("#usa");
const canada = $("#canada");
const otherCountry = $("#otherCountry");
const spr = $("#spr");

country.on("change", function() {
  countryPossibilities.hide();
  switch (this.value) {
    case "UNITED STATES":
      usa.show();
      break;
    case "CANADA":
      canada.show();
      break;
    default:
      otherCountry.show();
  }
  countrySubDiv.val("");
  spr.val("");
});

countrySubDiv.on("change",function(){
  spr.val(this.value);
});
.countryPossible{
  display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div>
  <label for="country">*Country</label>
  <select name="country" id="country" required>
    <option value="">CHOOSE…</option>
    <option value="UNITED STATES">UNITED STATES</option>
    <option value="CANADA">CANADA</option>
    <option value="AFGHANISTAN">AFGHANISTAN</option>
  </select>
</div>
<div id="usa" class="countryPossible">
  <label for="state">*State</label>
  <select name="state">
    <option value="">CHOOSE...</option>
    <option value="ALABAMA">ALABAMA</option>
    <option value="ALASKA">ALASKA</option>
  </select>
</div>
<div id="canada" class="countryPossible">
  <label for="province">*Province</label>
  <select name="province">
    <option value="">CHOOSE...</option>
    <option value="ALBERTA">ALBERTA</option>
    <option value="BRITISH COLUMBIA">BRITISH COLUMBIA</option>
  </select>
</div>
<div id="otherCountry" class="countryPossible">
  <label for="region">*Region</label>
  <input type="text" name="region" placeholder="Region">
</div>
<div>
  <input type="text" name="spr" id="spr">
</div>

\$\endgroup\$
0
0
\$\begingroup\$

I modified the Javascript so that it will work without modification if you add additional selects for other countries.

var countries = [];
        var x = document.getElementsByClassName("countryPossible");
        for(var i=0; i<x.length; i++){
            if (x[i].getAttribute('id')=='otherCountry'){
                // ignore
            }else{
                countries.push(x[i].getAttribute('id'));
            }
        }
        $("#na_country").on("load change",function(){
          $(".countryPossible").hide();
          $(".countryPossible select, .countryPossible input").prop( "disabled", true );
          const country=this.value.toLowerCase().replaceAll(' ', '-')
          if (countries.includes(country)){
              $("#"+country).show();
              $("#"+country+" select").prop( "disabled", false );
          } else {
              $("#otherCountry").show();
              $("#otherCountry input").prop( "disabled", false );
          }
        });
        $(function () {
            $("#na_country").change();
        });
.countryPossible{display: none;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<label for="na_country" class="form-label">Country</label>
<div class="input-group has-validation">
        <select class="form-select" id="na_country" name="na_country" required>
            <option  value="United States" >*** United States ***</option>
            <option  value="Afghanistan" >Afghanistan</option>
            <option  value="Albania" >Albania</option>
            <option  value="..." >...</option>
            <option  value="Canada" >*** Canada ***</option>
            <option  value="Cape Verde Island" >Cape Verde Island</option>
            <option  value="..." >...</option>
            <option  value="Mexico" >*** Mexico ***</option>
            <option  value="Micronesia" >Micronesia</option>
            <option  value="..." >...</option>
            <option  value="Zimbabwe" >Zimbabwe</option>
        </select>
    <div class="invalid-feedback">
     Please select a valid country.
    </div>
</div>

<div id="united-states" class="countryPossible">
 <label for="na_state" class="form-label">State</label>
 <div class="input-group has-validation">
        <select class="form-select" id="na_state" name="na_state" required>
            <option  value="AL" >Alabama</option>
            <option  value="AK" >Alaska</option>
            <option  value="AZ" >Arizona</option>
            <option  value="AR" >Arkansas</option>
            <option  value="CA" >California</option>
            <option  value="CO" >Colorado</option>
            <option  value=".." >...</option>
            <option  value="WY">Wyoming</option>
            <option  value="AA" >AA (U.S. Armed Forces Americas)</option>
            <option  value="AE" >AE (U.S. Armed Forces Europe)</option>
            <option  value="AP" >AP (U.S. Armed Forces Pacific)</option>
        </select>
    <div class="invalid-feedback">
     Valid state required.
    </div>
 </div>
</div>

<div id="canada" class="countryPossible">
 <label for="na_province" class="form-label">Province/Territory</label>
 <div class="input-group has-validation">
        <select class="form-select" id="na_province" name="na_province" required>
            <option  value="AB" >Alberta</option>
            <option  value="BC" >British Columbia</option>
            <option  value=".." >...</option>
            <option  value="SK" >Saskatchewan</option>
            <option  value="YT" >Yukon</option>
        </select>
    <div class="invalid-feedback">
        Valid province required.
    </div>
 </div>
</div>

<div id="mexico" class="countryPossible">
 <label for="na_mexico" class="form-label">State</label>
 <div class="input-group has-validation">
        <select class="form-select" id="na_mexico" name="na_mexico" required>
            <option  value="Aguascalientes" >Aguascalientes</option>
            <option  value="Baja California" >Baja California</option>
            <option  value="Baja California Sur" >Baja California Sur</option>
            <option  value="..." >...</option>
            <option  value="Yucatan" >Yucatan</option>
            <option  value="Zacatecas" >Zacatecas</option>
        </select>
    <div class="invalid-feedback">
     Valid state required.
    </div>
 </div>
</div>

<div id="otherCountry" class="countryPossible">
 <label for="na_region" class="form-label">Region</label>
 <div class="input-group has-validation">
  <input type="text" class="form-control" id="na_region" name="na_region" required>
    <div class="invalid-feedback">
   Region required.
  </div>
 </div>
</div>

\$\endgroup\$
9
  • \$\begingroup\$ Welcome to Code Review! You have presented an alternative solution, but haven't reviewed the code. Please edit to show what aspects of the question code prompted you to write this version, and in what ways it's an improvement over the original. It may be worth (re-)reading How to Answer. \$\endgroup\$ Commented Feb 1, 2022 at 17:12
  • \$\begingroup\$ This works, but I don't see how it is an improvement on Kruga's solution. \$\endgroup\$
    – Pslice
    Commented Feb 3, 2022 at 21:11
  • \$\begingroup\$ Adding additional selects for other countries only requires modifications to the html. The other solution requires modification to the html and the JavaScript. \$\endgroup\$ Commented Feb 5, 2022 at 8:48
  • \$\begingroup\$ To demonstrate the improvement I added Mexico States. To do that with the original code would have required several changes to the original JavaScript. The improved code only requires changes to the html. \$\endgroup\$ Commented Feb 5, 2022 at 11:46
  • \$\begingroup\$ I'm referring to the selected answer (Kruga) which only requires modifications to the HTML for additional countries. There is also Louys Patrice Bessette's solution which does require changes to the javascript, but the answer chosen does not. \$\endgroup\$
    – Pslice
    Commented Feb 7, 2022 at 22:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.