mohsin_jamshaid
mohsin_jamshaid

Reputation: 36

Debounce Slim Select Search Event with Stimulus Controller Ruby on Rails

Question forum on slim-select: https://github.com/brianvoe/slim-select/discussions/541

Hi, I'm using Ruby on Rails with Stimulus JS and have been using slim select for the past 6 months in my project. My team has got the task of updating performance this quarter. So as a part we are moving the searching part through ajax via slimselect and it is working. The only thing I'm looking at this point is to have a debounce search functionality so we are not hitting the server on every key press. It would be great if somebody can help me achieving this.

import { Controller } from "@hotwired/stimulus"
import SlimSelect from 'slim-select'
import debounce from "debounce";
import { authToken } from '../global'

export default class extends Controller {
  connect() {
    this.initializeInstance();
  }

  disconnect() {
    this.slimSelect.destroy();
  }

  initializeInstance() {
    this.searchIt = this.searchIt.bind(this);

    const options = {
      select: this.element,
      settings: {
        hideSelectedOption: true,
        placeholderText: this.placeholder,
        allowDeselect: true,
        maxValuesShown: 100,
      }
    }

    if (this.optionsUrl) {
      options.settings.searchingText = 'Searching...'
      options.events = {
        search: this.searchIt,
        afterOpen: () => {
          this.slimSelect.search('');
        }
      }
    }

    this.slimSelect = new SlimSelect(options)

    // Auto-populate the select with data from the prepopulateUrl
    // This comes in handy for applying filters to the select
    if (this.prepopulateUrl) {
      fetch(this.prepopulateUrl, {
        method: 'GET',
        headers: {
          'Accept': 'application/json',
          'X-CSRF-Token': authToken(),
        },
      })
      .then(response => response.json())
      .then(data => {
        const fieldMappings = this.fieldMappings;
        const options = data.map(item => ({ text: item[fieldMappings.text], value: item[fieldMappings.value] }));
        const selectedValues = data.map(item => item[fieldMappings.value]);
  
        this.slimSelect.setData(options);
        this.slimSelect.setSelected(selectedValues);
      })
      .catch(error => {
        console.error('Error:', error);
      });
    }
  }

  reinitializeInstance() {
    this.disconnect();
    this.initializeInstance();
  }

  get optionsUrl () {
    return this.element.dataset.optionsUrl
  }

  get placeholder () {
    return this.element.getAttribute('placeholder')
  }

  get fieldMappings() {
    return JSON.parse(this.element.dataset.fieldsMapping)
  }

  get prepopulateUrl() {
    return this.element.dataset.prepopulateUrl
  }

  searchIt (search, currentData) {
    return new Promise((resolve, reject) => {
      const url = this.optionsUrl.replace('${search}', search);

      fetch(url, {
        method: 'GET',
        headers: {
          'Accept': 'application/json',
          'X-CSRF-Token': authToken(),
        },
      })
      .then((response) => response.json())
      .then(data => {
        const fieldMappings = this.fieldMappings;

        // Take the data and create an array of options
        // excluding any that are already selected in currentData
        const options = data.filter((obj) => {
                          return !currentData.some((optionData) => {
                            return optionData.value === obj[fieldMappings.value]
                          })
                        })
                        .map((item) => {
                          return {
                            text: item[fieldMappings.text],
                            value: item[fieldMappings.value],
                          }
                        })

        resolve([...options, ...currentData]);
      })
    });
  }
}

I've tried something like this to achieve and it works when I type something in the search except for the first time when I open the search.

if (this.optionsUrl) {
      options.settings.searchingText = 'Searching...'
      options.events = {
        search: debounce(this.searchIt, 300),
        afterOpen: () => {
          this.slimSelect.search('');
        }
      }
}

On opening the search for the first time it gives me:

enter image description here

Have also tried debounce(this.slimSelect.search(''), 300) in the afterOpen but still the same issue

Upvotes: 0

Views: 284

Answers (1)

BingoYoan
BingoYoan

Reputation: 41

Here is a debounce function I use in all of my ROR search forms:

import { Controller } from "@hotwired/stimulus";
import debounce from "debounce";

export default class extends Controller {
  initialize() {
    this.submit = debounce(this.submit.bind(this), 300);
  }
  submit() {
    this.element.requestSubmit();
  }
}

After that in your html you need to do:

<%= form_with url: '/events', method: :get, data: { turbo_frame: "events", turbo_action: "advance", controller: "form", action: "input->form#submit" } do %>
...
<% end %>

<%= turbo_frame_tag "events" do %>
    <section>
        <%= render @events %>
    </section>
<% end %>

Upvotes: 0

Related Questions