I have a dead simple while loop that works just fine in Safari, and by logic it should also work on chrome.
It doesn't. In chrome, it works only the first time and the subsequent times it fails in a Uncaught RangeError: Maximum call stack size exceeded
The application:
- a standard select input with options. Some options (text) start with a
-
. Others, don't - My JS acts on
change()
of said select input, and if the selected optionstartsWith()
the-
, then it checks its previous option and selects that, if that option does not start with-
. Otherwise, it goes to the previous of the previous, etc, until an option without-
is found.
This is the dummy sample code:
Select Field Input
<select name="my-select" >
<option value="0">All</option>
<option value="value-1">Value 1</option>
<option value="value-1-child">- Value 1 child</option>
<option value="value-2">Value 2</option>
<option value="value-2-child-1">- Value 2 child 1</option>
<option value="value-2-child-2">- Value 2 child 2</option>
<option value="value-3">Value 3</option>
</select>
The jQuery
/**
* Function to find previous option in select that does NOT have a "-" prefix.
* If current option HAS such prefix, it scans previous options until one WITHOUT it is found (the parent) and selects that one.
* If current option HAS NO such prefix, it can skip.
*/
function select_parent_instead_of_current() {
jQuery('select[name="my-select"]').change(function(){
if ( jQuery.trim( jQuery('select option:selected').text() ).startsWith('-') ) {
var a = jQuery('select option:selected').prev();
while ( jQuery.trim( a.text() ).startsWith('-') ) {
a = a.prev();
}
if ( jQuery.trim( a.text() ).startsWith('-') === false ) {
jQuery('select[name="my-select"] option:contains(' jQuery.trim( a.text() ) ')').attr('selected', 'selected').change();
}
}
});
}
/**
* Fire the function on ready, and other events I require
*/
jQuery( document ).on( 'custom_event_one custom_event_two ready', function( event, data ) {
select_parent_instead_of_current();
});
Clicking on - Value 2 child 2
option, should select Value 2
, and this should work no matter if we click once or 100 times on that option.
In safari, this works like a charm.
CodePudding user response:
Your
jQuery('select[name="my-select"] option:contains(' jQuery.trim( a.text() ) ')').attr('selected', 'selected').change();
is causing problems. Use String.prototype.trim
, which has been available everywhere for more than a decade, instead of jQuery's deprecated and unhelpful jQuery.trim
., and change the selected
property of the element to true with .prop
instead of .attr
. With that, the recursive .change()
call won't result in an overflow. You can also consider using $
instead of jQuery
so as to be less verbose.
function select_parent_instead_of_current() {
const select = $('select[name="my-select"]');
select.change(() => {
// Either repeat [name="my-select"] again to make sure the right <select> is chosen
// Or save the select in a variable name beforehand and .find
const option = select.find('option:selected');
if (!option.text().trim().startsWith('-')) {
// reduce indentation hell by returning early
return;
}
// consider using a more precise variable name than `a`
let prevOption = option.prev();
while (prevOption.text().startsWith('-')) {
prevOption = prevOption.prev();
}
if (!prevOption.text().trim().startsWith('-')) {
prevOption.prop('selected', true).change();
}
});
}
select_parent_instead_of_current();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select name="my-select" >
<option value="0">All</option>
<option value="value-1">Value 1</option>
<option value="value-1-child">- Value 1 child</option>
<option value="value-2">Value 2</option>
<option value="value-2-child-1">- Value 2 child 1</option>
<option value="value-2-child-2">- Value 2 child 2</option>
<option value="value-3">Value 3</option>
</select>
Rather than checking the text, a quicker option would be to check whether the element being iterated over has child
in its value, and use .prevAll
to select previous siblings that don't have child
in the attribute.
function select_parent_instead_of_current() {
const select = $('select[name="my-select"]');
select.change(() => {
const option = select.find('option:selected');
if (option.is('[value*=child]')) {
option
.prevAll(':not([value*=child])')
.eq(0) // only want the first matching sibling
.prop('selected', true)
.change();
}
});
}
select_parent_instead_of_current();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select name="my-select" >
<option value="0">All</option>
<option value="value-1">Value 1</option>
<option value="value-1-child">- Value 1 child</option>
<option value="value-2">Value 2</option>
<option value="value-2-child-1">- Value 2 child 1</option>
<option value="value-2-child-2">- Value 2 child 2</option>
<option value="value-3">Value 3</option>
</select>