Home > Enterprise >  Maximum call stack size exceeded on simple while( startsWith ), on Chrome/Firefox only
Maximum call stack size exceeded on simple while( startsWith ), on Chrome/Firefox only

Time:01-12

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:

  1. a standard select input with options. Some options (text) start with a -. Others, don't
  2. My JS acts on change() of said select input, and if the selected option startsWith() 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>

  • Related