0

I have two select controls on my web page (Bootstrap Select2):

<div class="form-group">
    <label>Client: </label>
    <select id="ddlClient" class="form-control select2 subselect">
        <option value="0">Select client</option>
    </select>
</div>

<div class="form-group">
    <label>Policy: </label>
    <select id="ddlPolicy" class="form-control select2">
        <option value="0">...</option>
    </select>
</div>

I have a listener on the first one ddlClient to fill values of the second one based on client selection:

$(document).ready(function () {
    $('.content').on('change', 'select.subselect', function () {

    var subItems = "<option value='0'>... select policy ... </option>";

    var idSelect = $('#ddlClient').val();

    // fill list
    $.getJSON("@Url.Action("GetPolicies", "Home")", {id: idSelect }, function (data) {

        $.each(data, function (index, item) {
            subItems += "<option value='" + item.value + "'>" + item.text + "</option>";
        });

        $("#ddlPolicy").html(subItems);
        $("#ddlPolicy").val('0');
    });     
});

Now, in some cases I have to programatically change both dropdowns and the problem is, that the function below (line 3 and 4) obviously do not wait for the change event to finish. So, policy is not selected corectly:

$('#ddlClient').val(someClientValue);
$('#ddlClient').trigger('change'); //trigger change so that SELECT2 changes text
$('#ddlPolicy').val(somePolicyValue);
$('#ddlPolicy').trigger('change'); //trigger change so that SELECT2 changes text

How do I wait for change to finish on ddlClient before calling $('#ddlPolicy').val(somePolicyValue);?

What I've tried and doesn't work (How to get jQuery to wait until an effect is finished?):

$('#ddlClient').val(someClientValue);
$('#ddlClient').change().promise().done(function () {
   $('#ddlPolicy').val(somePolicyValue);
   $('#ddlPolicy').trigger('change'); 
}

I have also tried implementing .when:

$('#ddlClient').val(someClientValue);
$.when($('#ddlClient').trigger('change')).then(function () {
    $('#ddlPolicy').val(somePolicyValue);
    $('#ddlPolicy').trigger('change');
}

but the problem remains.

TheMixy
  • 1,049
  • 15
  • 36
  • You can see this [thread](https://stackoverflow.com/questions/44797915/jquery-wait-on-change-event) may helpful. – Yinqiu Apr 06 '21 at 02:25
  • I tried it with `$.when` but no difference. `$('#ddlPolicy').val(somePolicyValue);` gets hit before `change` event finishes (see my update above) – TheMixy Apr 06 '21 at 12:35
  • By "wait for change to finish" do you actually mean "wait for `getJSON` request to complete and populate my second dropdown with values so I can select one of them"? – Jamiec Apr 06 '21 at 12:43
  • @Jamiec: yes, wait for `getJSON` within the `change` event to finish, and then run `$('#ddlPolicy').val(somePolicyValue); $('#ddlPolicy').trigger('change');` – TheMixy Apr 06 '21 at 12:46
  • Yeah, you see _I got that_ but your questions _doesn't say that_! – Jamiec Apr 06 '21 at 12:47
  • Do you know the answer or did you just want to comment on my wording? – TheMixy Apr 06 '21 at 12:49
  • Yes, I do. I'm half way through writing an answer, despite the somewhat spikey nature of your last comment! – Jamiec Apr 06 '21 at 12:49
  • No spikeness intented (I'm not a native English speaker). – TheMixy Apr 06 '21 at 12:51

3 Answers3

0

There is no "built-in" way to notify jQuery that your change event performs some asynchronous work that must be awaited. So your best option is to move the logic for populating the second dropdown out into its own function, which returns a Promise. From the change event you needn't wait for this promise to finish, but from your code where you need to wait for it to do so before performing the next action you await it.

First extenalise pretty much exactly what you have, except rather than using callbacks, use promises:

function loadSubitems() {
    
    var idSelect = $('#ddlClient').val();

    // fill list
    return $.getJSON("@Url.Action("GetPolicies", "Home")", {id: idSelect })
            .then(data => {
                   var subItems = "<option value='0'>... select policy ... </option>";

                   $.each(data, function (index, item) {
                      subItems += "<option value='" + item.value + "'>" + item.text + "</option>";
                   });

                    $("#ddlPolicy").html(subItems)
                                   .val('0');
            });     
}

this code returns a promise, but you don't need to wait for it to complete in an already asynchronous context like an event handler:

$(document).ready(function () {
    $('.content').on('change', 'select.subselect', loadSubitems);
});

However in your code where you need to set it, make sure that is itself an async function and await as necessary

(async function(){
   $('#ddlClient').val(someClientValue);
   await loadSubitems();
   $('#ddlPolicy').val(somePolicyValue);
})();
Jamiec
  • 133,658
  • 13
  • 134
  • 193
  • After `$('#ddlClient').val(someClientValue);` I have to call also `$('#ddlClient').trigger('change');`, because I'm using Select2 dropdown. Otherwise selection (visually) doesn't change. This way then the function `loadSubitems();` gets called 2 times. Once by `await` and also by the `trigger`. Is there a workaround? – TheMixy Apr 06 '21 at 13:08
  • see: https://select2.org/programmatic-control/add-select-clear-items (for selecting Select2 control) – TheMixy Apr 06 '21 at 13:09
  • Note: it doesn't work also. `ddlPolicy` gets populated, but not selected. – TheMixy Apr 06 '21 at 13:47
  • thank you for your time, I found a different solution – TheMixy Apr 06 '21 at 14:13
0

.trigger() according to documentation can pass extra parameters to event so I will go with that. For example you can alter your change event to perform callback when its done.
for example

$('#ddlClient').on('change', (event, callback = undefined) => {
    // something
    if (callback != undefined) {
        callback();
    }
});

and then instead of calling them in order, pass the second trigger as callback to first trigger

$('#ddlClient').val(12);
$('#ddlClient').trigger('change', triggerDdlPolicy);

const triggerDdlPolicy = () => {
    $('#ddlPolicy').val(12);
    $('#ddlPolicy').trigger('change');
}

Full snippet
$(() => {
    triggerDdlClient();
});

$('#ddlClient').on('change', (event, callback = undefined) => {
    // something
    if (callback != undefined) {
        callback();
    }
});

const triggerDdlClient = () => {
    $('#ddlClient').val(12);
    $('#ddlClient').trigger('change', triggerDdlPolicy);
}

const triggerDdlPolicy = () => {
    $('#ddlPolicy').val(12);
    $('#ddlPolicy').trigger('change');
}
ciekals11
  • 2,032
  • 1
  • 10
  • 24
0

I managed to solve the problem by adding an input parameter to the change event (i.e. param=undefined) and then runing policies dropdown differently based on who is triggering the change event.

TheMixy
  • 1,049
  • 15
  • 36