subcategory will not show as parent category

I am trying to get my subcategory based on the category here is my code

    <div class="form-group">
    <label>Category</label>
    <select id="categoryList" class="form-control" name="category_id" required>
       <?php
  $categories = $this->crud_model->get_categories()->result_array();
  foreach ($categories as $key => $category):?>
            <option value="<?php echo $category['slug']; ?>"><?php echo $category['name']; ?></option>
       <?php endforeach; ?>
    </select>

    <label>Subcategory</label>
    <select id="subcategoryList" class="form-control" name="subcategory_id" required disabled>
      <?php
      $sub_categories = $this->crud_model->get_sub_categories($category['id']);
      foreach ($sub_categories as $sub_category): ?>
    <option id="sub_category-<?php echo $sub_category['id'];?>" name="sub_category"  class="parent-<?php $selected_category_id == $sub_category['id'] ?> subcategory" value="<?php echo $sub_category['slug']; ?>"><?php echo $sub_category['name']; ?></option> 
    <?php  endforeach; ?>
    </select>

the jquery

$('#categoryList').on('change', function () {
    $("#subcategoryList").attr('disabled', false); //enable subcategory select
    $("#subcategoryList").val("");
    $(".subcategory").attr('disabled', true); //disable all category option
    $(".subcategory").hide(); //hide all subcategory option
    $(".parent-" + $(this).val()).attr('disabled', false); //enable subcategory of selected category/parent
    $(".parent-" + $(this).val()).show(); 
});

but it will only show the last category’s subcategories how can I fix this?

alternatively i was using this code before

<div class="form-group">
        <label>Category</label>
        <select id="categoryList" class="form-control" name="category_id" required>
           <?php
      $categories = $this->crud_model->get_categories()->result_array();
      foreach ($categories as $key => $category):?>
                <option value="<?php echo $category['slug']; ?>"><?php echo $category['name']; ?></option>
           <?php endforeach; ?>
        </select>

        <label>Subcategory</label>
        <select id="subcategoryList" class="form-control" name="subcategory_id" required disabled>
          <?php
          $sub_categories = $this->crud_model->get_sub_categories($category['id']);
          foreach ($sub_categories as $sub_category): ?>
        <option id="sub_category-<?php echo $sub_category['id'];?>" name="sub_category"  class="parent-<?php $selected_category_id == $sub_category['id'] ?> subcategory" value="<?php echo $sub_category['slug']; ?>"><?php echo $sub_category['name']; ?></option> 
        <?php  endforeach; ?>
        </select>
        
    </div>

jquery

$(function () {
    $("select.dependent").each(function (i, sel) {
        var original = $(this).clone(),
            dependent = $("<select></select>").attr({
                name: this.name
            }).insertAfter(this)
            this.name = "categories_" + this.name

            $("optgroup", this).replaceWith(function () {
                return "<option>" + this.label + "</option>"
            })
            $("option:first",this).prop("selected",true)
            $(this).removeAttr("multiple").on("change", function () {
                var cat = $(this).val()
                dependent.html(original.children("[label='" + cat + "']").html())
            }).change()
            console.log(this)
    })
    
})

but since I need 2 selects one individually for sub-categories that I can put an onchange=”filter(this)” for it that show the result only based on the subcategory I’ve changed my code to first part so if there is a way that I can do that in the second part that can fix my problem too

Answer

Looking at your originally posted code let’s review what it’s doing:

<label>Subcategory</label>
    <select id="subcategoryList" class="form-control" name="subcategory_id" required disabled>
      <?php
      $sub_categories = $this->crud_model->get_sub_categories($category['id']);
      foreach ($sub_categories as $sub_category): ?>
    <option id="sub_category-<?php echo $sub_category['id'];?>" name="sub_category"  class="parent-<?php $selected_category_id == $sub_category['id'] ?> subcategory" value="<?php echo $sub_category['slug']; ?>"><?php echo $sub_category['name']; ?></option> 
    <?php  endforeach; ?>
    </select>

Since PHP is processed on the server side and no other AJAX calls are in play here the line $sub_categories = $this->crud_model->get_sub_categories($category['id']); will only ever produce the sub-categories to the last category in the block above. This is the root of your problem, you needed to iterate over the categories again to get the sub-categories for each category.

I have altered your code combining the two together to show what changes were made to foreach over the categories, jQuery.hide() the subcategory selects, and then jQuery.show() only the one selected when a category is selected.

<script>
  $('#categoryList').on('change', function () {
    var catId = $(this).val();

    // This will go through each item on the page with class subcategory
    $('.subcategory-group').each(function(key, val) {
      $(val).hide();
      $(val).find('select').attr('disabled', true);
    });

    // Find the correct subcategory using the category id, not make it disabled, set the value to nothing
    $("#subcategory-" + catId).show();
    $("#subcategoryList-" + catId).attr('disabled', false).val("").show();
  });
</script>

<div class="form-group">
  <label for="categoryList">Category</label>
  <select id="categoryList" class="form-control" name="category_id" required>
      <?php
      $categories = $this->crud_model->get_categories()->result_array();
      foreach ($categories as $key => $category):?>
        <option value="<?php echo $category['slug']; ?>"><?php echo $category['name']; ?></option>
      <?php endforeach; ?>
  </select>

  <?php
  // Iterate over the categories again
  foreach($categories as $category):
  ?>
      <div id="subcategory-<?php echo $category['slug']; ?>" class="subcategory-group">
        <!-- Set the label correctly for ADA compliance, showing the name of the category so you can see it working -->
        <label for="subcategoryList-<?php echo $category['slug']?>" class="subcategory-label">Subcategory - <?php echo $category['name']; ?></label>
        <!-- Set the id to something unique using the category id, set a class named subcategory, keep the name so your PHP form can handle it -->
        <select id="subcategoryList-<?php echo $category['slug']?>" class="form-control" name="subcategory_id" required disabled>
          <?php
          // Keeping the code you had which is correct
          $sub_categories = $this->crud_model->get_sub_categories($category['id']);
          foreach ($sub_categories as $sub_category): ?>
            <option id="sub_category-<?php echo $sub_category['id']; ?>" name="sub_category"
                    class="categories custom-select"
                    value="<?php echo $sub_category['slug']; ?>"><?php echo $sub_category['name']; ?></option>
          <?php endforeach; ?>
        </select>
      </div>
  <?php endforeach; ?>
</div>

The changes include looping over the categories again to get all the subcategories that’s why you only have the last one.

The next thing was to use the category id to be unique in the subcategory groups.

Following by using the proper for attribute on the labels for ADA compliance.

Adding a subcategory class on the <select> tags so that you can find (and hide) all the ones initially and then show only the one that they picked.

EDIT: Changed to use the $category['slug'] instead of $category['id'] per specifications not listed in original request