title: Dynamic Cascading Dropdown using AJAX
body: <p><strong>Objective:</strong></p>
  
  <p>Based on the example found <a href="http://www.dnasir.com/github/jquery-cascading-dropdown/demo.html" rel="nofollow noreferrer">here</a>. Populating dependent drop-downs with the data parsed in the <code>getData()</code> function using ajax calls. Currently my example is working with static data found in the <code>ajax-mocks.js</code> file, but I am unable to understand how to parse the data properly into the drop-downs, as well as populating the other drop-downs as previously done with the sample mockjax data calls.</p>
  
  <p><strong>Resources:</strong></p>
  
  <p><a href="https://github.com/dnasir/jquery-cascading-dropdown" rel="nofollow noreferrer">jQuery Cascading Dropdown</a></p>
  
  <p><a href="http://knockoutjs.com/documentation/json-data.html" rel="nofollow noreferrer">KnockoutJS - Loading/Saving Json Data</a></p>
  
  <p><a href="https://github.com/jakerella/jquery-mockjax" rel="nofollow noreferrer">jQuery Mockjax</a></p>
  
  <p><a href="https://github.com/select2/select2" rel="nofollow noreferrer">Select2</a></p>
  
  <p><strong>functions.php</strong></p>
  
  <p><em>Renders HTML to woocommerce front-end product page</em></p>
  
  <pre><code>function add_custom_toga_fields() {
      if( has_term('toga', 'product_cat' ) ) {
          ?&gt;
          &lt;p class="label-description"&gt;&lt;span&gt;1. Details&lt;/span&gt;&lt;/p&gt;
          &lt;table id="graduation" class="custom-fields .bs-docs-example" cellspacing="0"&gt;
              &lt;tbody&gt;
                  &lt;tr&gt;
                      &lt;td class="label"&gt;&lt;label for="institution"&gt;Institution&lt;span class="required"&gt;*&lt;/span&gt;&lt;/label&gt;&lt;/td&gt;
                      &lt;td class="value"&gt;
                          &lt;select name="institution" id="institution" class="step1 select2"&gt;
                              &lt;option value=""&gt;Select institution&lt;/option&gt;
                          &lt;/select&gt;
                      &lt;/td&gt;
                  &lt;/tr&gt;
                  &lt;tr&gt;
                      &lt;td class="label"&gt;&lt;label for="level"&gt;Level of Award&lt;span class="required"&gt;*&lt;/span&gt;&lt;/label&gt;&lt;/td&gt;
                      &lt;td class="value"&gt;
                          &lt;select name="level" id="level" class="step2 select2"&gt;
                              &lt;option value=""&gt;Select level&lt;/option&gt;
                          &lt;/select&gt;
                      &lt;/td&gt;
                  &lt;/tr&gt;
                  &lt;tr&gt;
                      &lt;td class="label"&gt;&lt;label for="faculty"&gt;Faculty&lt;span class="required"&gt;*&lt;/span&gt;&lt;/label&gt;&lt;/td&gt;
                      &lt;td class="value"&gt;
                          &lt;select name="faculty" id="faculty" class="step3 select2"&gt;
                              &lt;option value=""&gt;Select Faculty&lt;/option&gt;
                          &lt;/select&gt;
                      &lt;/td&gt;
                  &lt;/tr&gt;
                  &lt;tr&gt;
                      &lt;td class="label"&gt;&lt;label for="ceremony-date"&gt;Ceremony Date&lt;span class="required"&gt;*&lt;/span&gt;&lt;/label&gt;&lt;/td&gt;
                      &lt;td class="value"&gt;
                          &lt;input name="ceremony" type="text" id="ceremony"&gt;
                      &lt;/td&gt;
                  &lt;/tr&gt;
                  &lt;tr&gt;
                      &lt;td&gt;
                          &lt;h4&gt;Matches &lt;img src="&lt;?php echo get_stylesheet_directory_uri(); ?&gt;/assets/icons/ajax-loader.gif" data-bind="visible: loading" /&gt;&lt;/h4&gt;
                          &lt;ul class="colour-results-list" data-bind="foreach: colours, visible: colours().length &gt; 0"&gt;
                              &lt;li style="background: red;"&gt;
                                  &lt;span class="colour-hat" data-bind="text: colour" style="background: yellow;"&gt;&lt;/span&gt;
                              &lt;/li&gt;
                          &lt;/ul&gt;
                      &lt;/td&gt;
                  &lt;/tr&gt;
              &lt;/tbody&gt;
          &lt;/table&gt;
          &lt;?php
      }
  }
  add_action( 'woocommerce_before_variations_form', 'add_custom_toga_fields' );
  </code></pre>
  
  <p><strong>class-system-public.php</strong></p>
  
  <p><em>Get data from database and process it to correct format</em></p>
  
  <pre><code>public function get_data() {
      global $wpdb;
  
      $result = array();
  
      $records = $wpdb-&gt;get_results("CALL get_json_data");
      foreach($records as $record) {
          $obj = new stdClass();
          $obj-&gt;institution = $record-&gt;universityid;
          $obj-&gt;level = $record-&gt;levelid;
          $obj-&gt;faculty = [];
  
          $faculties = $wpdb-&gt;get_results("CALL get_courses_and_colour_by_university_and_level($obj-&gt;institution, $obj-&gt;level)");
          foreach($faculties as $faculty) {
              $facObj-&gt;name = $faculty-&gt;name;
              array_push($obj-&gt;faculty, $facObj-&gt;name);
          }
          array_push($result, $obj);
      }
      echo json_encode($result);
  
      wp_die();
  }
  </code></pre>
  
  <p><strong>custom-dropdown.js</strong></p>
  
  <p><em>Builds Dependent Cascading Drop-down</em></p>
  
  <pre><code>jQuery(document).ready(function() {
      // Apply Select 2
      jQuery(".select2").select2();
  
      function getInstitutions() {
          var results = [
              { label: 'Test 1', value: 1 }, 
              { label: 'Test 2', value: 2 }, 
              { label: 'Test 3', value: 3 },
          ]
          return results;
      }
  
      function viewmodel() {
          this.colours = ko.observableArray([]);
          this.loading = ko.observable(false);
      }
  
      var graduation = new viewmodel();
  
      ko.applyBindings(graduation, document.getElementById('graduation'));
  
      jQuery('#graduation').cascadingDropdown({
          selectBoxes: [
              {
                  selector: '.step1',
                  source: getInstitutions()
              },
              {
                  selector: '.step2',
                  requires: ['.step1'],
                  source: function(request, response) {
                      jQuery.getJSON('/api/levels', request, function(data) {
                          var selectOnlyOption = data.length &lt;= 1;
                          response(jQuery.map(data, function(item, index) {
                              return {
                                  label: item,
                                  value: item,
                                  selected: selectOnlyOption
                              };
                          }));
                      });
                  }
              },
              {
                  selector: '.step3',
                  requires: ['.step1', '.step2'],
                  requireAll: true,
                  source: function(request, response) {
                      jQuery.getJSON('/api/faculties', request, function(data) {
                          response(jQuery.map(data, function(item, index) {
                              return {
                                  label: item,
                                  value: item,
                                  selected: index == 0
                              };
                          }));
                      });
                  },
                  onChange: function(event, value, requiredValues, requirementsMet) {
                      if(!requirementsMet) return;
  
                      graduation.loading(true);
  
                      var ajaxData = requiredValues;
                      ajaxData[this.el.attr('name')] = value;
                      jQuery.getJSON('/api/colours', ajaxData, function(data) {
                          graduation.colours(data);
                          graduation.loading(false);
                      });
                  }
              }
          ]
      });
  });
  </code></pre>
  
  <p><strong>ajax-mock.js</strong></p>
  
  <p><em>Some mockjax data to simulate ajax call</em></p>
  
  <pre><code>// Some mockjax code to simulate Ajax calls
  var colourList = [
      {
          faculty: [8, 16],
          institution: 2,
          level: "Bachelors",
          colour: 'Red'
      },
      {
          faculty: [32, 64],
          institution: 3,
          level: "Doctorate",
          colour: 'Green'
      },
      {
          institution: 2,
          level: "Bachelors",
          faculty: [8],
          colour: 'Blue'
      },
      {
          faculty: [16],
          institution: 3,
          level: "Masters",
          colour: 'Purple'
      },
      {
          faculty: [16],
          institution: 3,
          level: "Masters",
          colour: 'Pink'
      },
      {
          faculty: [16, 32],
          institution: 1,
          level: "Masters",
          colour: 'Brown'
      },
      {
          level: 2,
          faculty: ["Msc Business Information System Management"],
          institution: 3,
          colour: 'Gray'
      }
  ];
  
  getData();
  function getData() {
      var data = { 'action': 'get_data' };
      var deferred = new jQuery.Deferred();
  
      return jQuery.post(ajaxurl, data, function(response) {
          var obj = JSON.parse(response);
          results = obj;
      }).done(function() {
          return deferred.resolve(results);
      }).fail(function() {
      });
  }
  
  function arrayIntersect(a, b) {
      return jQuery.grep(a, function(i) {
          return jQuery.inArray(i, b) &gt; -1;
      });
  }
  
  function arrayToInt(array) {
      var output = [];
  
      for(var i=0;i&lt;array.length;i++) {
          if(array[i] &amp;&amp; !isNaN(+array[i])) output.push(+array[i]);
      }
  
      return output;
  }
  
  function arrayToFloat(array) {
      var output = [];
  
      for(var i=0;i&lt;array.length;i++) {
          if(array[i] &amp;&amp; !isNaN(parseFloat(array[i]))) output.push(parseFloat(array[i]));
      }
  
      return output;
  }
  
  function getColours(institution, level, faculty) {
      var _institution = arrayToFloat([].concat(institution)),
          _level = arrayToInt([].concat(level)),
          _faculty = arrayToInt([].concat(faculty));
  
      return jQuery.grep(colourList, function(item, index) {
          var i = true, l = true, f = true;
  
          if(_institution.length) {
              i = jQuery.inArray(item.institution, _institution) &gt; -1;
          }
  
          if(_level.length) {
              l = jQuery.inArray(item.level, _level) &gt; -1;
          }
  
          if(_faculty.length) {
              f = arrayIntersect(item.faculty, _faculty).length &gt; 0;
          }
  
          return !!(i &amp;&amp; l &amp;&amp; f);
      });
  }
  
  function getLevels(level, faculty) {
      var colours = getColours(null, level, faculty);
  
      var institutions = jQuery.map(colours, function(colour) { return colour.institution; });
      institutions.sort(asc);
      return arrayUnique(institutions);
  }
  
  function getUniversities(institution, faculty) {
      var colours = getColours(institution, null, faculty);
  
      var levels = jQuery.map(colours, function(colour) { return colour.level; });
  
      levels.sort(asc);
      return arrayUnique(levels);
  }
  
  function getFaculties(institution, level) {
      var colours = getColours(institution, level, null);
  
      var faculties = [];
      jQuery.each(colours, function(index, item) {
          faculties = arrayUnique(faculties.concat(item.faculty));
      });
      faculties.sort(asc);
      return faculties;
  }
  
  function arrayUnique(array) {
      var a = array.concat();
      for(var i=0; i&lt;a.length; ++i) {
          for(var j=i+1; j&lt;a.length; ++j) {
              if(a[i] === a[j])
                  a.splice(j--, 1);
          }
      }
  
      return a;
  }
  
  function asc(a, b) {
      return a - b;
  }
  
  jQuery.mockjax({
      url: ajaxurl,
      contentType: 'application/json; charset=utf-8',
      responseTime: 1000,
      response: function(settings) {
          this.responseText = JSON.stringify(getLevels(settings.data.level, settings.data.faculty));
      }
  });
  
  jQuery.mockjax({
      url: '/api/levels',
      contentType: 'application/json; charset=utf-8',
      responseTime: 1000,
      response: function(settings) {
          this.responseText = JSON.stringify(getUniversities(settings.data.institution, settings.data.faculty));
      }
  });
  
  jQuery.mockjax({
      url: '/api/faculties',
      contentType: 'application/json; charset=utf-8',
      responseTime: 1000,
      response: function(settings) {
          this.responseText = JSON.stringify(getFaculties(settings.data.institution, settings.data.level));
      }
  });
  
  jQuery.mockjax({
      url: '/api/colours',
      contentType: 'application/json; charset=utf-8',
      responseTime: 1000,
      response: function(settings){
          this.responseText = JSON.stringify(getColours(settings.data.institution, settings.data.level, settings.data.faculty));
      }
  });
  </code></pre>
  
  <p><strong>Admin-Ajax.php</strong></p>
  
  <p><em>Response received from admin-ajax.php</em></p>
  
  <p><a href="https://i.stack.imgur.com/XHDXv.jpg" rel="nofollow noreferrer"><img src="https://i.stack.imgur.com/XHDXv.jpg" alt="enter image description here"></a></p>
  
  <p><strong>Extra Notes</strong></p>
  
  <p>I have been stuck for a while trying to figure out how I can replace the Mockjax calls with the ajax calls from the server but I have not managed to understand all the technologies utilized fully.</p>
  
  <p>Sincerely thank you, to whoever takes their time to help me guide me into the right direction. Your help is greatly appreciated at this point.</p>