简体   繁体   中英

Dynamic Cascading Dropdown using AJAX

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:

jQuery Cascading Dropdown

KnockoutJS - Loading/Saving Json Data

jQuery Mockjax

Select2

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:

  • load set A and set D from the server
  • require a selection from set A to retrieve set B from the server,
  • require a selection from set B to retrieve set C from the server,
  • when selections in set A, B and C, filter the list of D

Knockout features

In knockout, you can create this dependency chain using three features:

  • observableArray to store the server responses for each set
  • subscribe to trigger a new request once a selection changes
  • pureComputed to automatically filter a list of objects based on several data-sources & selections

The flow order

In the example below, I show how to implement this in a typical knockout pattern:

  1. Load institutions and colours async.
  2. When institutions load, knockout renders them in a <select>
  3. The selected value is bound to selection.institution
  4. When this value changes, load faculties async
  5. Do the same to load levels
  6. When a level is selected, filter colours that match all three

The beauty of knockout's dependency management is that you can update any of these lists at any time, and the UI will render correctly. Eg you can update your colours source 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 colours available. Also, the example contains es6 features that you might need to transpile for older browsers.

 const App = function() { // The data sources this.institutions = ko.observableArray([]); this.faculties = ko.observableArray([]); this.levels = ko.observableArray([]); const colours = ko.observableArray([]); // The selections made in the UI this.selected = { institution: ko.observable(null), faculty: ko.observable(null), level: ko.observable(null) }; // The filter logic this.availableColours = ko.pureComputed(() => { if (colours().length === 0 || this.selected.institution() === null || this.selected.faculty() === null || this.selected.level() === null) { return []; } const inst = this.selected.institution(); const fac = this.selected.faculty(); const lvl = this.selected.level(); return colours() .filter(c => c.institution === inst && c.faculty.includes(fac) && c.level === lvl ); }).extend({"deferred": true}); // Loading the data: // 1. always load institutions & colours mockAsync(getInstitutions) .then(this.institutions); mockAsync(getColours) .then(colours); // 2. load faculties after instution this.selected.institution.subscribe( selection => { this.faculties([]); /* do something with inst. in get URL */ mockAsync(getFaculties) .then(this.faculties) } ); // 3. load levels after faculty this.selected.faculty.subscribe( selection => { this.levels([]); /* do something with inst. in get URL */ mockAsync(getLevels) .then(this.levels) } ); } ko.applyBindings(new App()); function mockAsync(fn) { let _cb = () => {}; setTimeout(() => _cb(fn()), 200 + Math.random() * 300); return { then: cb => _cb = cb } }; function getLevels() { return ["Doctorate", "Bachelors", "Masters"]; }; function getInstitutions() { return [1, 2, 3]; }; function getFaculties(){ return [8, 16, 32, 64]; }; function getColours() { return [{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"}]; }; 
 <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script> <select data-bind="options: institutions, optionsCaption: 'Select an institution', value: selected.institution"></select> <select data-bind="options: faculties, optionsCaption: 'Select a faculty', value: selected.faculty, disable: !selected.institution()"></select> <select data-bind="options: levels, optionsCaption: 'Select a level', value: selected.level, disable: !selected.faculty()"></select> <h3>Available colours:</h3> <ul data-bind="foreach: availableColours"> <li data-bind="text: colour"></li> </ul> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM