javascriptphpjsoncascadingdropdown

Set PHP array to work with dropdown list created from Json object


I have been stuck on this for a few days now and could use some expert advice!

So, as a personal/learning project I'm making a fun little 'character relationship tracker' for my friends' D&D games. With this, I have a DB table of dummy dungeon masters as well as the game(s) they are running for the purpose of testing code. I am trying to create a cascading dropdown list generated from a mix of PHP and encoded to JSON as part of the submit new character form. It works! Except, in the database containing the info for the options, some have only one game in the list for a DM while others have multiple games for the same DM. For those singles, the JS is listing out every single letter as a different choice instead of just the single full option. I have tried remaking the PHP twice to try resolving this and shuffled through a handful of JS attempts to no avail. These changes have either broken the code completely or resulted in no changes.

This fiddle is what it is doing with my best attempt

as for the actual code:

HTML

<div class="selectdiv">
        <select name="chargm" id="charadm" onChange="chgm(this.value);" required>
            <option value="" selected="true" disabled>Game Master</option>
            <?php foreach ($gameMasters as $masterName) { echo "<option value='". $masterName . "'>" . $masterName . "</option>"; } ?>
        </select>
    </div>
    <div class="selectdiv">
        <select name="chargame" id="chargm" required>
            <option value="" selected="true" disabled>Game Name</option>
        </select>
    </div>

here is the PHP (I know it's super messy and redundant but I couldn't get it to work any other way for some reason and it does its job?)

//database connection stuff
$sqls = "SELECT * FROM datgames;";
$stmts = mysqli_query($conn, $sqls);
$resultcheck = mysqli_num_rows($stmts);
$gameMasters = array(); //create empty array of game masters.
$gameData = array(); //set up game list data array

//check if db can be read before contiuning.
if($resultcheck > 0){
  while($row = mysqli_fetch_assoc($stmts)){ //while there are rows, add to array
    $gameMasters[] = $row["datgamDM"]; //fill the gameMasters array with all gm's
    $gmdm = $row["datgamDM"]; //define gmdm as the game master of the row

    //copy existing info in gameData to preserve data
    $anotm = $gameData;

    //clear game data to reset it to avoid repeats
    unset($gameData);

    //create the key => value pair
    $tmpar[$gmdm] = $row["datgamName"];

    //merge the temp arrays and apply them to the global array
    $gameData = array_merge_recursive($anotm, $tmpar);

    //clear the temporary arrays to avoid repeats
    unset($anotm);
    unset($tmpar);
  }
}else{ exit(); } //if db can't be reached, break the code.

$gameMasters = array_unique($gameMasters);

//print_r($gameData); //making sure the array is right. this line is removed once this is working

and the exact JSON output from the PHP currently with this loop

{
    "Reid":[
        "Curse of Strahd",
        "ufkck"],
    "bob":[
        "Curse of Strahd",
        "fffs"]
    ,"jomama":"blaal",
    "taco":"salff"
};

and the JS adapted from divy3993's answer here

var list = <?php echo json_encode($gameData); ?>;

    function chgm(value) {
      if (value.length == 0) document.getElementById("chargm").innerHTML = "<option></option>";
      else {
        var games = "";
        for (categoryId in list[value]) {

          games += "<option>" + list[value][categoryId] + "</option>";
        }
        document.getElementById("chargm").innerHTML = games;
      }
    }

The question in short: What am I doing wrong in either PHP (most likely the cause) or Javascript that is causing the words in single-object groups to split into letters instead of showing the full word as the only option for the second drop down option?

Or rather, how do I get the PHP to make single-entries to show up as a multidimensional array while keeping the key so it shows up as an array in the JSON object?


Solution

  • The trouble with using array_merge_recursive() is that it can produce an inconsistent structure as it creates depth.

    For instance, see that a 1st level key contains an indexed subarray if there is more than one element, but creates an associative array on the first level when only one element exists. I explain this here and provide a simple demonstration.

    A result set from mysqli's query() is instantly traversable using foreach(), so I recommend that concise technique which sets up intuitive associative array accessing.

    $result = [];
    foreach ($conn->query("SELECT datgamDM, datgamName FROM datgames") as $row) {
        $result[$row["datgamDM"]][] = $row["datgamName"];
    }
    exit(json_encode($result));
    

    This way, you have a consistent structure -- an associative array of indexed arrays. In other words:

    {
        "Reid":["Curse of Strahd","ufkck"],
        "bob":["Curse of Strahd","fffs"],
        "jomama":["blaal"],
        "taco":["salff"]
    }
    

    Then life only gets easier. You only need to iterate like:

    for (index in list[value]) {
    

    As for the technique that you are using to generate the select/option markup -- that's not the way I would do it, there are multiple ways to do it, there are TONS of pages on StackOverflow that explain these options for you.

    I generally don't like the UI of providing form instructions or labels as the top option of a field. I recommend that you give your form fields <label>s so that the options only contain real options.


    As a completely different alternative, if you don't want to keep modifying the DOM every time the user makes a selection change, you could print ALL of the secondary select fields with their options preloaded, then "hide" them all. Then as the user changes the primary select field, merely "show" the field with the related id. This does of course create more html markup (which may or may not be attractive depending on your data volume), but it greatly reduces the complexity of the javascript code since all of the dynamic processing is done on page load. If one day, you wanted to make your primary select field a "multi-select", then having toggle-able secondary fields will work nicely. ..."horses for courses" and all that.