I've got the following code which shows and hides select menus using a basic options chain parent/child relationship. Once there is a change I need to call a function.
Currently I am doing it as demonstrated below, triggering an event on change for each <select>
, which then calls the function. In real life this is an ajax call but that is irrelevant to the question.
The code I have below works. The problem I would like to solve is how I can accomplish this without relying on a change event since it triggers the function multiple times unnecessarily. I'd like to simply place a function call at the end of the $.each()
loop when the parent is changed but because of the animation involved it will fail since the elements in question are sometimes still visible/hidden. I solved this by placing a callback inside each slideUp()
and slideDown()
but this is the exact reason I end up triggering things multiple times.
I have a strong feeling that the answer I am looking for uses jQuery promise()
which states:
The .promise() method returns a dynamically generated Promise that is resolved once all actions of a certain type bound to the collection, queued or not, have ended.
That is exactly what I want to do but I am utterly confused by promise()
and it's proper usage. I get the concept, but I could use some help understanding how I can queue all those change events into one promise()
and then call my function when they are all complete.
$('select[data-parent="true"]').change(function() {
var parentValue = +this.value;
var parentId = $(this).data('parent_id');
$('div[data-parent_id="' + parentId + '"]').each(function() {
var parentValues = $(this).data('parent_values');
if (parentValues.indexOf(parentValue) !== -1) {
$(this).slideDown(1000, function() {
$(this).find('select').change();
});
} else {
$(this).slideUp(1000, function() {
$(this).find('select').change();
});
}
});
});
$('select').on('change', function() {
var results = '';
$('div.option').each(function() {
results += $(this).find('select').prop('name') + ' IS VISIBLE: ' + +$(this).is(':visible') + '<br>';
});
$('#results').html(results);
});
div {
padding: 5px 10px;
}
div.child {
display: none
}
select {
width: 200px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div class="option">
<select name="container" data-parent="true" data-parent_id="10">
<option value="">--- Select a container---</option>
<option value="1">Box</option>
<option value="2">Bag</option>
<option value="3">Pallet</option>
</select>
</div>
<div class="child option" data-parent_id="10" data-parent_values="[1]">
<select name="box color">
<option value="100">Red</option>
<option value="200">Blue</option>
</select>
</div>
<div class="child option" data-parent_id="10" data-parent_values="[2]">
<select name="bag color">
<option value="300">Green</option>
<option value="400">Yellow</option>
</select>
</div>
<div class="child option" data-parent_id="10" data-parent_values="[3]">
<select name="pallet color">
<option value="500">Black</option>
<option value="600">Yellow</option>
</select>
</div>
<div id="results">
</div>
As jQuery animations can return promises .promise()
and using .map
instead of .each
- the code can be simply:
$('select[data-parent="true"]').change(function() {
var parentValue = +this.value;
var parentId = $(this).data('parent_id');
$.when.apply($, $('div[data-parent_id="' + parentId + '"]').map(function(i, v) {
var parentValues = $(this).data('parent_values');
if (parentValues.indexOf(parentValue) !== -1) {
return $(this).slideDown(1000).promise();
} else {
return $(this).slideUp(1000).promise();
}
})).done(function() {
var results = '';
$('div.option').each(function() {
results += $(this).find('select').prop('name') + ' IS VISIBLE: ' + +$(this).is(':visible') + '<br>';
});
$('#results').html(results);
});
});
div {
padding: 5px 10px;
}
div.child {
display: none
}
select {
width: 200px;
margin: 4px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div class="option">
<select name="container" data-parent="true" data-parent_id="10">
<option value="">--- Select a container---</option>
<option value="1">Box</option>
<option value="2">Bag</option>
<option value="3">Pallet</option>
</select>
</div>
<div class="child option" data-parent_id="10" data-parent_values="[1]">
<select name="box color">
<option value="100">Red</option>
<option value="200">Blue</option>
</select>
</div>
<div class="child option" data-parent_id="10" data-parent_values="[2]">
<select name="bag color">
<option value="300">Green</option>
<option value="400">Yellow</option>
</select>
</div>
<div class="child option" data-parent_id="10" data-parent_values="[3]">
<select name="pallet color">
<option value="500">Black</option>
<option value="600">Yellow</option>
</select>
</div>
<div id="results">
</div>
An hour of experimenting has yielded the following solution. Create an array of $.Deferred()
objects and use $.when
to resolve them. The tricky part came from this answer which explains how to pass an array to $.when
. Hope this helps someone struggling with jQuery deferred objects like I was:
$('select[data-parent="true"]').change(function() {
var deferred = [];
var parentValue = +this.value;
var parentId = $(this).data('parent_id');
$('div[data-parent_id="' + parentId + '"]').each(function(i, v) {
deferred[i] = $.Deferred();
var parentValues = $(this).data('parent_values');
if (parentValues.indexOf(parentValue) !== -1) {
$(this).slideDown(1000, function() {
deferred[i].resolve();
});
} else {
$(this).slideUp(1000, function() {
deferred[i].resolve();
});
}
});
$.when.apply($, deferred).done(function() {
var results = '';
$('div.option').each(function() {
results += $(this).find('select').prop('name') + ' IS VISIBLE: ' + +$(this).is(':visible') + '<br>';
});
$('#results').html(results);
});
});
div {
padding: 5px 10px;
}
div.child {
display: none
}
select {
width: 200px;
margin: 4px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<div class="option">
<select name="container" data-parent="true" data-parent_id="10">
<option value="">--- Select a container---</option>
<option value="1">Box</option>
<option value="2">Bag</option>
<option value="3">Pallet</option>
</select>
</div>
<div class="child option" data-parent_id="10" data-parent_values="[1]">
<select name="box color">
<option value="100">Red</option>
<option value="200">Blue</option>
</select>
</div>
<div class="child option" data-parent_id="10" data-parent_values="[2]">
<select name="bag color">
<option value="300">Green</option>
<option value="400">Yellow</option>
</select>
</div>
<div class="child option" data-parent_id="10" data-parent_values="[3]">
<select name="pallet color">
<option value="500">Black</option>
<option value="600">Yellow</option>
</select>
</div>
<div id="results">
</div>