Blu3
Blu3

Reputation: 143

How to retain dependent dropdown values using Select2 and Ajax

I have a script that populates the state dropdown based on country id and while everything is working great I can only save the countries dropdown option on page reload but not the state option using html localStorage.

Here is my code:

$(document).ready(function() {
  var country_id = null;
  var state_id = null;

  $('#country').select2();
  $('#state').select2();
  $('#city').select2();

  $('select[name="country"]').on('change', function() {
    var country_id = $(this).val();
    if (country_id) {
      $.ajax({
        url: "/world/getStates.php",
        type: "GET",
        data: {
          'country_id': country_id
        },
        dataType: "json",
        success: function(data) {
          $('select[name="state"]').empty();
          $('select[name="city"]').empty();
          $('select[name="state"]').append('<option value="">Select State</option>');
          $.each(JSON.parse(data), function(key, value) {
            $('select[name="state"]').append('<option value="' + value.id + '">' + value.name + '</option>');
          });
        }
      });
    } else {
      $('select[name="state"]').empty();
    }
  });

  $('select[name="state"]').on('change', function() {
    var country_id = $('#country').val();
    var state_id = $(this).val();
    if (state_id) {
      $.ajax({
        url: "/world/getCities.php",
        type: "GET",
        data: {
          'country_id': country_id,
          'state_id': state_id
        },
        dataType: "json",
        success: function(data) {
          $('select[name="city"]').empty();
          $('select[name="city"]').append('<option value="">Select City</option>');
          $.each(JSON.parse(data), function(key, value) {
            $('select[name="city"]').append('<option value="' + value.id + '">' + value.name + '</option>');
          });
        }
      });
    } else {
      $('select[name="city"]').empty();
    }
  });

  $('#country').val("value from localStorage").trigger('change');
  $('#state').val("value from localStorage").trigger('change');
});
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"></script>

<label for="country">Country</label>
<select class="csc-select" name="country" id="country">
  <option value="">Select Country</option>
  <option>Australia</option>
  <option>Denmark</option>
  <option>Japan</option>
  <option>Norway</option>
  <option>Switzerland</option>
</select>

<label for="state">State</label>
<select class="csc-select" name="state" id="state">
  <option value="">Select State</option>
</select>

<label for="city">City</label>
<select class="csc-select" name="city" id="city">
  <option value="">Select City</option>
</select>

So when I call this after the first on change for the country select, it selects the country based on the localStorage value and triggers the change but it does not do the same for the state, any ideas what I am missing here?

Upvotes: 1

Views: 2109

Answers (2)

Tomalak
Tomalak

Reputation: 338308

when I call this after the first on change for the country select, it selects the country based on the localStorage value and triggers the change but it does not do the same for the state, any ideas what I am missing here?

When you trigger the change event for your #country list, the list of countries already contains values.

When you trigger the change event for your #state list immediately after that, the list is still empty. There can't be any value selected, so the change event does nothing.

You need to wait until the state list is populated, and then trigger the change event.

// this works immediately, because there are countries in your HTML
$('#country').val("value from localStorage").trigger('change');

// this needs to happen in the `success` callback of country Ajax call
$('#state').val("value from localStorage").trigger('change');

The alternative is that you create a temporary option first:

$('#state').append("temporary <option> created from localStorage");
$('#state').val("value from localStorage").trigger('change');

This way you would not have to wait.


That being said, Select2 supports remote data, you don't have to write the Ajax requests or the <option> creation yourself.

$("#country").select2({
  ajax: {
    url: "/world/getCountries.php"
  },
  placeholder: 'Pick a country',
  minimumInputLength: 1
}).change(function () {
  $("#state").val("").trigger("change");
});

$("#state").select2({
  ajax: {
    url: "/world/getStates.php",
    data: (params) => {
      // add selected country ID to URL params 
      params.country_id = $("#country").val();
      return params;
    }
  },
  placeholder: 'Pick a state',
  minimumInputLength: 1
});

// initialize selection from previous state...
$("#country").append('<option value="5">Switzerland</option>');
$("#country").val("5");
$("#state").append('<option value="9">Appenzell</option>');
$("#state").val("9");


// server side mock-up -------------------------------------------------
const countries = [
  {id: 1, text: 'Australia'},
  {id: 2, text: 'Denmark' },
  {id: 3, text: 'Japan'},
  {id: 4, text: 'Norway'},
  {id: 5, text: 'Switzerland'}
];
const states = [
  {id: 1, text: 'New South Wales', country_id: 1},
  {id: 2, text: 'Victoria', country_id: 1},
  {id: 3, text: 'Hovedstaden', country_id: 2},
  {id: 4, text: 'Midtjylland', country_id: 2},
  {id: 5, text: 'Hokkaido', country_id: 3},
  {id: 6, text: 'Shikoku', country_id: 3},
  {id: 7, text: 'Northern Norway', country_id: 4},
  {id: 8, text: 'Southern Norway', country_id: 4},
  {id: 9, text: 'Appenzell', country_id: 5},
  {id: 10, text: 'Zürich', country_id: 5},
];

$.mockjaxSettings.logging = 1;
$.mockjax({
  url: "/world/getCountries.php",
  contentType: "application/json",
  response: function(settings) {
    this.responseText = {
      results: countries.filter(item =>
        !settings.data.term || item.text.toLowerCase().includes(settings.data.term.toLowerCase())
      )
    };
  }
});
$.mockjax({
  url: "/world/getStates.php",
  contentType: "application/json",
  response: function(settings) {
    console.log(settings.data);
    this.responseText = {
      results: states.filter(item =>
        item.country_id == settings.data.country_id && (
          !settings.data.term || item.text.toLowerCase().includes(settings.data.term.toLowerCase())
        )
      )
    };
  }
});
select {
  width: 200px;
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-mockjax/2.6.0/jquery.mockjax.min.js"></script>

<select id="country"></select>
<select id="state"></select>

Upvotes: 1

Swati
Swati

Reputation: 28522

Your options inside state dropdown is loaded after the ajax success is executed so your other code doesn't wait for that and .val() fired before only that's the reason the value is not marked as selected inside state dropdown. Now , to fix this you can move that part inside success function of ajax and then call your change event after the options are appended inside state dropdown.

Demo Code :

$(document).ready(function() {

  var country_id = 1 //localStorage.getItem("select2CountryValue");
  var state_id = 3 //localStorage.getItem("select2StateValue");
  var page_load = true; //added this 
  var data = [{
    "id": 1,
    "name": "xyz_State1"
  }, {
    "id": 2,
    "name": "xyz_State2"
  }, {
    "id": 3,
    "name": "xyz_State3"
  }] //this is just the demo datas
  $('#country').select2();
  $('#state').select2();

  $('select[name="country"]').on('change', function() {
    var country_id = $(this).val();
    //localStorage.setItem("select2CountryValue", country_id);
    if (country_id) {
      /*$.ajax({
        url: "/world/getStates.php",
        type: "GET",
        data: {
          'country_id': country_id
        },
        dataType: "json",
        success: function(data) {
          console.log(data);
          $('select[name="city"]').empty();*/
      $('select[name="state"]').empty();
      $('select[name="state"]').append('<option value="">Select State</option>');
      $.each(data, function(key, value) {
        $('select[name="state"]').append('<option value="' + value.id + '">' + value.name + '</option>');
      });
      //check if the change is called on page load
      if (page_load == true) {
        $('#state').val(state_id).trigger('change'); //assign slected value after elemnt option is added in dom
        page_load = false; //add this so that next time this doesn't get execute
      }
      /* }
        });*/
    } else {
      $('select[name="state"]').empty();
    }
  });

  $('#country').val(country_id).trigger('change');

});
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/css/select2.min.css" />
<script src="https://code.jquery.com/jquery-3.2.1.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.0/js/select2.full.min.js"></script>

<p>
  <span>Country</span>
  <select class="csc-select" name="country" id="country">
    <option value="">Select Country</option>
    <option value="1">
      xyz
    </option>
    <option value="2">
      xyz2
    </option>
  </select>
</p>
<p>
  <span>State</span>
  <select class="csc-select" name="state" id="state">
    <option value="">Select State</option>
  </select>
</p>

Upvotes: 2

Related Questions