2

I have a JSON file (json/cities.json) which associates the states of my country to its cities in the following form:

{
    "State #1": [
        "City #1 from State #1",
        "City #2 from State #1",
        "City #3 from State #1"
    ],
    "State #2": [
        "City #1 from State #2",
        "City #2 from State #2",
        "City #3 from State #2"
    ]
}

I also have a HTML select with the states, like this:

<select id="state" name="state">
    <option value="State #1"> State #1 </option>
    <option value="State #2"> State #2 </option>
</select>

and an empty HTML select for the cities:

<select id="city" name="city"></select>

What I am trying to do is to fill the HTML select of the cities with the JSON values filtered by the key (state).

I am using the following jQuery script:

$('#state').on('change', function () {
    var state = $(this).val(), city = $('#city');
    $.getJSON('json/cities.json', function (result) {
        $.each(result, function (i, value) {
            if (i === state) {
                 var obj = city.append($("<option></option>").attr("value", value).text(value));
                 console.log(obj);
            }
        });
    });
});

The problem is that the select that should be filled with the cities doesn't even change while console.log returns the following markup:

<select name="city" id="city">
    <option value="City #1 form State #1, City #2 from State #1, City #3 from State #1">
        City #1 from State #1, City #2 from State #1, City #3 from State #1
    </option>
</select>

That is, the values are returned as one value where it should be multiple values (each one separated by comma).

Musa
  • 96,336
  • 17
  • 118
  • 137
StillBuggin
  • 280
  • 1
  • 3
  • 14
  • Seems like this `if (i === state) {` should be this `if (value === state) {` but if your `console.log()` is running, then the `value` of the states must be different. –  Jan 01 '16 at 21:29
  • 1
    ...oh, nevermind. You're using `i` to represent a property name instead of an index. That's confusing. –  Jan 01 '16 at 21:31
  • Should I change that? – StillBuggin Jan 01 '16 at 21:34
  • 2
    You don't have to, but `i` is usually used for counters or indices. But the issue is that you should be selecting the state you want, and then iterating its cities. Use `$.each(result[state], ...` and get rid of the `if()` statement. Like this: https://jsfiddle.net/33j4v2rm/ –  Jan 01 '16 at 21:37

3 Answers3

3

You're iterating over the states instead of the cities. result[state] gives you the array of cities iterate over that.

P.S. The url part in the code is just to get it work in the snippet

$('#state').on('change', function () {
    var state = $(this).val(), city = $('#city');
    city.empty();
    var url = URL.createObjectURL(new Blob(['{"State #1":["City #1 from State #1","City #2 from State #1","City #3 from State #1"],"State #2":["City #1 from State #2","City #2 from State #2","City #3 from State #2"]}'], {type:'application/json'}));
    $.getJSON(url, function (result) {
        if (result[state]){
            $.each(result[state], function (i, value) {
               city.append($("<option></option>").attr("value", value).text(value));
            });
        }
    });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select id="state" name="state">
    <option value="State #1"> State #1 </option>
    <option value="State #2"> State #2 </option>
</select>
<select id="city" name="city"></select>
Musa
  • 96,336
  • 17
  • 118
  • 137
1

My proposal:

$('#state').on('change', function () {
  var state = $(this).val(), city = $('#city');
  $.getJSON('json/cities.json', function (result) {
    var values = result[state];
    if (values != undefined && values.length > 0) {
      city.find('option').remove();
      $(values).each(function(index, element) {
        city.append($("<option></option>").attr("value", element).text(element));
      });
    }
  });
});
gaetanoM
  • 41,594
  • 6
  • 42
  • 61
  • 1
    Why are you iterating over `results` when you already have the key `state`? i.e. `result[state]` – Musa Jan 01 '16 at 21:52
  • @Musa From one point of view you are right but let's assume the content is not in the result variable the result will be undefined so before all a test is necessary. I know my answer is not perfect. In any case I upvoted your answer. Let me know what do you think. – gaetanoM Jan 01 '16 at 21:56
  • I get what you're saying but I'd use an if statement for that check and still clear the select either way. – Musa Jan 01 '16 at 22:03
  • @Musa I appreciate too much your answer, indeed I upvoted you. Now I corrected the answer. Thanks so much for your remarks – gaetanoM Jan 01 '16 at 22:12
0

Here's a working example. I changed a few things to get this to work; comments in the code.

Firstly, i embedded the JSON in the document to make it work in a code snippet. It's pretty trivial to change the source of this variable. You can go ahead and alter the JSON in this version too, and you'll see the changes update.

Instead of iterating over the states and seeing if it matches the selected state, I make use of the structure in the JSON to index the cities by name. This is far more efficient if there are many states, and easier to program anyway.

I also added the missing iteration over the cities in the list under the state identifier.

I added a clearing of the cities in the list so that when the list is changed as well, or else the list of cities would include all the cities from all the states ever selected.

Though not strictly necessary, i think adding the selected, disabled option to the state <select> is a nice touch - it forces the user to select a state and force the city list update ( otherwise state 1 is selected by default but more work needs to be done to populate the list for that initial selection, since the onchange won't have been set when the elements were first defined ).

/*
I'm embedding the JSON right in the page, but you'd 
use AJAX, of course.  You might want to pull this data just 
once or - if, it changes frequently and 
interaction with the form is prolonged, pull it
periodically, behind the scenes with a setInterval so the 
user doesn't have to wait for a web request.
*/
function get_state_data(){
   return JSON.parse($('#json').val())
};

// set the event handler after the doc is ready - 
// make sure it's set _after_ the elements it changes 
// are in the DOM
$(document).ready(function() {
  $('#state').on('change', function() {
    var state = $(this).val(),
      city = $('#city'),
      data = get_state_data();
    city.html(""); // clear out old cities
    // make use of the key->value structure of json,
    // rather than iterating over all cities
    if (data[state] === undefined)
      alert("No such state in data! :( ");
    else {
      //we have to iterate over each city
      for (c in data[state]) {
        city.append(
           $("<option></option>").attr("value", data[state][c]).text(data[state][c])
        );
      }
    }
  });
});
textarea {
  width: 95%;
  margin: auto;
  height: 10em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>
  <textarea id='json'>
    { "State #1": [ "City #1 from State #1", "City #2 from State #1", "City #3 from State #1" ], "State #2": [ "City #1 from State #2", "City #2 from State #2", "City #3 from State #2" ] }
  </textarea>
</div>
<select id="state" name="state">
  <option disabled selected value=''>Select a state</option>
  <option value="State #1">State #1</option>
  <option value="State #2">State #2</option>
</select>
<select id="city" name="city"></select>
erik258
  • 14,701
  • 2
  • 25
  • 31