Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
172 views
in Technique[技术] by (71.8m points)

html - JavaScript application bug: calling a function inside another throws a "Maximum call stack size exceeded" error

I have a list of Bootstrap 4 cards, displayed on the 12 columns grid.

I have made a filter by year and a search-box. As can be seen in the example below, the search expression is matched against the title of each card and the selected year is matched against the value of the data-year attribute.

class CardsFilteringComponent {
  constructor() {
    this.filterableCardsList = document.querySelector('.filterableItems');
    this.items = this.filterableCardsList.querySelectorAll('.card-container');
    this.searchBox = this.filterableCardsList.querySelector('#searchBoxInput');
    this.searchBtn = this.filterableCardsList.querySelector('#searchBoxSubmitBtn');
    this.selectBox = this.filterableCardsList.querySelector('#selectYear');
    this.isHiddenClass = 'd-none';
  }

  handleSearchButtonClick(target) {
    // call the filter function
    //this.handleSelectChange(target);
    const expression = this.searchBox.value.toLowerCase();

    [...this.items].forEach(item => {
      const cardTitle = item.querySelector('.title').textContent;
      const showItem = cardTitle.toLowerCase().indexOf(expression) > -1;
      const method = showItem ? 'remove' : 'add';
      item.classList[method](this.isHiddenClass);
    });
  }

  handleSelectChange(target) {
    // call the search function
    //this.handleSearchButtonClick(target);
    const {value } = target;

    [...this.items].forEach(item => {
      const year = item.dataset.year;
      const showItem = year === value || value === 'ALL';
      const method = showItem ? 'remove' : 'add';
      item.classList[method](this.isHiddenClass);
    });
  }

  init() {
    this.searchBtn.addEventListener('click', () => {
      this.handleSearchButtonClick();
    });

    this.searchBox.addEventListener('keyup', e => {
      if (e.keyCode === 13) {
        this.handleSearchButtonClick();
      }
    });

    this.selectBox.addEventListener('change', e => {
      this.handleSelectChange(e.target);
    });
  }
}
const cardsFilteringComponent = new CardsFilteringComponent();
cardsFilteringComponent.init();
.cards-grid>[class*='col-'] {
  display: flex;
  flex-direction: column;
  margin-bottom: 25px;
}

.cards-grid .card {
  flex-grow: 1;
  display: flex;
  flex-direction: column;
  background: #fff;
  border-radius: 2px;
  transition: all 0.3s ease-in-out;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.11);
}
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet"/>
<div class="container-fluid">
  <div class="filterableItems my-4">
    <div class="input-group mb-2">
      <input type="text" class="form-control" placeholder="Search" id="searchBoxInput">
      <div class="input-group-append">
        <button class="btn btn-secondary" id="searchBoxSubmitBtn" type="button">
          <i class="fa fa-search"></i>
        </button>
      </div>
    </div>
    <div class="form-group mb-2">
      <select id="selectYear" class="form-control">
        <option value="ALL" selected>All</option>
        <option value="2019">2019</option>
        <option value="2020">2020</option>
        <option value="2021">2021</option>
      </select>
    </div>
    <div class="row cards-grid">
      <div class="card-container col-xs-12 col-md-4" data-year="2019">
        <div class="card">
          <div class="cardImage">
            <img src="https://picsum.photos/1200/800/?gravity=north" alt="Lorem ipsum dolor" class="img-fluid">
          </div>
          <div class="cardContent p-3">
            <h5 class="title">Lorem ipsum dolor</h5>
            <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Repellat voluptates modi hic molestias quam doloremque. Accusantium odio blanditiis, amet placeat distinctio, quam magni, nobis perferendis error dicta perspiciatis quod delectus.</p>
          </div>
          <div class="mt-auto p-3 text-right text-muted small">Published on January 2<sup>nd</sup> 2019</div>
        </div>
      </div>
      <div class="card-container col-xs-12 col-md-4" data-year="2020">
        <div class="card">
          <div class="cardImage">
            <img src="https://picsum.photos/1200/800/?gravity=south" alt="In, quod adipisci" class="img-fluid">
          </div>
          <div class="cardContent p-3">
            <h5 class="title">In, quod adipisci</h5>
            <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quia eaque qui ipsa quod facere autem voluptatem.</p>
          </div>
          <div class="mt-auto p-3 text-right text-muted small">Published on January 3<sup>rd</sup> 2020</div>
        </div>
      </div>
      <div class="card-container col-xs-12 col-md-4" data-year="2021">
        <div class="card">
          <div class="cardImage">
            <img src="https://picsum.photos/1200/800/?gravity=west" alt="Pariatur, sit, dolor" class="img-fluid">
          </div>
          <div class="cardContent p-3">
            <h5 class="title">Pariatur, sit, dolor</h5>
            <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum eaque repellat cumque pariatur dolores enim quibusdam nemo distinctio, dolore incidunt cupiditate ea excepturi est architecto amet tempore voluptatibus alias doloremque.</p>
          </div>
          <div class="mt-auto p-3 text-right text-muted small">Published on January 2<sup>nd</sup> 2021</div>
        </div>
      </div>
    </div>
  </div>
</div>
question from:https://stackoverflow.com/questions/65626218/javascript-application-bug-calling-a-function-inside-another-throws-a-maximum

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Your mistake is your you are calling one function that is calling the other function which in turns calls the original function and this goes on until Maximum call stack size exceeded error is raised.

What you could do instead is: save the values of both filters and apply them simultaneously.

  handleSearchButtonClick(target) {
    const expression = this.searchBox.value.toLowerCase();
    
    this.filters.search = expression; // save the expression
    this.applyFilter();
  }

  handleSelectChange(target) {
    const {value} = target;

    this.filters.year = value; // save the year
    this.applyFilter();
  }

  applyFilter() {
     // For each item

     // Filter by title first
     const cardTitle = ...;
     const showItemForSearch = cardTitle.toLowerCase().indexOf(this.filters.search) > -1;
      
     // Filter by year next
     const year = ...;
     const showItemForYear = year === this.filters.year || this.filters.year === 'ALL';
     
     // make it visible if both condition is met
     const showItem = showItemForSearch && showItemForYear;
     ...
   });
 }

Here is an updated fiddle: https://jsfiddle.net/9ra4y0cz/1/

class CardsFilteringComponent {
constructor() {
    this.isHiddenClass = 'd-none';

    this.filterableCardsList = document.querySelector('.filterableItems');
    this.items = this.filterableCardsList.querySelectorAll('.card-container');

    // saving the initial or default values of the filters
    this.filters = {
        search: '',
        year: 'ALL'
    };

    // saving each item into an array
    // makes it easier to manipulate the state of each item i.e. hidden or visible
    this.state = [];
    this.items.forEach(item => {
        this.state.push({
            node: item,
            hidden: false // initially saving all items as visible, will modify this later
        });
    });


    this.searchBox = this.filterableCardsList.querySelector('#searchBoxInput');
    this.searchBtn = this.filterableCardsList.querySelector('#searchBoxSubmitBtn');
    this.selectBox = this.filterableCardsList.querySelector('#selectYear');

    this.render(); // initial rendering, all items are visible
}

render() {
    // reading value from the state array and applying classname according to .hidden property
    this.state.forEach(item => {
        const method = item.hidden ? 'add' : 'remove';
        item.node.classList[method](this.isHiddenClass);
    });
}

applyFilter() {
    this.state.forEach(item => {
        // Filter by title first
        const cardTitle = item.node.querySelector('.title').textContent;
        const showItemForSearch = cardTitle.toLowerCase().indexOf(this.filters.search) > -1;

        // Filter by year next
        const year = item.node.dataset.year;
        const showItemForYear = year === this.filters.year || this.filters.year === 'ALL';

        // make it visible if both condition is met
        // .hidden is the opposite of that
        item.hidden = !(showItemForSearch && showItemForYear);
    });
    this.render(); // re-render since the state array may have changed
}

handleSearchButtonClick(target) {
    const expression = this.searchBox.value.toLowerCase();
    this.filters.search = expression; // save the expression
    
    this.applyFilter();
}

handleSelectChange(target) {
    const { value } = target;
    this.filters.year = value; // save the year
    
    this.applyFilter();
}

init() {
    this.searchBtn.addEventListener('click', () => {
        this.handleSearchButtonClick();
    });

    this.searchBox.addEventListener('keyup', e => {
        if (e.keyCode === 13) {
            this.handleSearchButtonClick();
        }
    });

    this.selectBox.addEventListener('change', e => {
        this.handleSelectChange(e.target);
    });
}
}
const cardsFilteringComponent = new CardsFilteringComponent();
cardsFilteringComponent.init();

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...