Objective:
Based on the example found here. Populating dependent drop-downs with the data parsed in the getData() function using ajax calls. Currently my example is working with static data found in the ajax-mocks.js 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.
Resources:
KnockoutJS - Loading/Saving Json Data
functions.php
Renders HTML to woocommerce front-end product page
function add_custom_toga_fields() {
if( has_term('toga', 'product_cat' ) ) {
?>
<p class="label-description"><span>1. Details</span></p>
<table id="graduation" class="custom-fields .bs-docs-example" cellspacing="0">
<tbody>
<tr>
<td class="label"><label for="institution">Institution<span class="required">*</span></label></td>
<td class="value">
<select name="institution" id="institution" class="step1 select2">
<option value="">Select institution</option>
</select>
</td>
</tr>
<tr>
<td class="label"><label for="level">Level of Award<span class="required">*</span></label></td>
<td class="value">
<select name="level" id="level" class="step2 select2">
<option value="">Select level</option>
</select>
</td>
</tr>
<tr>
<td class="label"><label for="faculty">Faculty<span class="required">*</span></label></td>
<td class="value">
<select name="faculty" id="faculty" class="step3 select2">
<option value="">Select Faculty</option>
</select>
</td>
</tr>
<tr>
<td class="label"><label for="ceremony-date">Ceremony Date<span class="required">*</span></label></td>
<td class="value">
<input name="ceremony" type="text" id="ceremony">
</td>
</tr>
<tr>
<td>
<h4>Matches <img src="<?php echo get_stylesheet_directory_uri(); ?>/assets/icons/ajax-loader.gif" data-bind="visible: loading" /></h4>
<ul class="colour-results-list" data-bind="foreach: colours, visible: colours().length > 0">
<li style="background: red;">
<span class="colour-hat" data-bind="text: colour" style="background: yellow;"></span>
</li>
</ul>
</td>
</tr>
</tbody>
</table>
<?php
}
}
add_action( 'woocommerce_before_variations_form', 'add_custom_toga_fields' );
class-system-public.php
Get data from database and process it to correct format
public function get_data() {
global $wpdb;
$result = array();
$records = $wpdb->get_results("CALL get_json_data");
foreach($records as $record) {
$obj = new stdClass();
$obj->institution = $record->universityid;
$obj->level = $record->levelid;
$obj->faculty = [];
$faculties = $wpdb->get_results("CALL get_courses_and_colour_by_university_and_level($obj->institution, $obj->level)");
foreach($faculties as $faculty) {
$facObj->name = $faculty->name;
array_push($obj->faculty, $facObj->name);
}
array_push($result, $obj);
}
echo json_encode($result);
wp_die();
}
custom-dropdown.js
Builds Dependent Cascading Drop-down
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 <= 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);
});
}
}
]
});
});
ajax-mock.js
Some mockjax data to simulate ajax call
// 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) > -1;
});
}
function arrayToInt(array) {
var output = [];
for(var i=0;i<array.length;i++) {
if(array[i] && !isNaN(+array[i])) output.push(+array[i]);
}
return output;
}
function arrayToFloat(array) {
var output = [];
for(var i=0;i<array.length;i++) {
if(array[i] && !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) > -1;
}
if(_level.length) {
l = jQuery.inArray(item.level, _level) > -1;
}
if(_faculty.length) {
f = arrayIntersect(item.faculty, _faculty).length > 0;
}
return !!(i && l && 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<a.length; ++i) {
for(var j=i+1; j<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));
}
});
Admin-Ajax.php
Response received from admin-ajax.php
Extra Notes
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.
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.

Your question contains a mix of PHP, jQuery, knockout, and many lines of code. I took the liberty to extract one core problem and write up an answer to that part of the question.
How to use knockout to create a nested list of dropdowns based on async data
The abstracted requirements
The way (I think) your system works, is that you:
Knockout features
In knockout, you can create this dependency chain using three features:
observableArrayto store the server responses for each setsubscribeto trigger a new request once a selection changespureComputedto automatically filter a list of objects based on several data-sources & selectionsThe flow order
In the example below, I show how to implement this in a typical knockout pattern:
institutionsandcoloursasync.institutionsload, knockout renders them in a<select>selection.institutionfacultiesasynclevelslevelis selected, filtercoloursthat match all threeThe beauty of knockout's dependency management is that you can update any of these lists at any time, and the UI will render correctly. E.g. you can update your
colourssource after having already made three selections, and the list will refresh.The example
Note that I used some random data from your snippet, so for many combinations there are no
coloursavailable. Also, the example contains es6 features that you might need to transpile for older browsers.