4
\$\begingroup\$

Here is code 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\$
2
\$\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\$
  • \$\begingroup\$ Thanks, I like this answer for removing the need for a switch. \$\endgroup\$ – Pslice Aug 10 '18 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\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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