I have implemented a ShuffleJS select dropdown but there are a few glitches

I want to use ShuffleJS (see https://vestride.github.io/Shuffle) to filter out items on a <select> list by typing my search keyword on the <select>‘s input text area and the items in the <select> dropdown to be filtered out dynamically as I type. Unfortunately I have not managed this. And so, I followed the idea in How to add shufflejs for select tag which suggests creating a DIY <select> of some sorts which then ShuffleJS seems to handle OK.

So, instead of using a <select>:

  1. I create a <textarea> to type my search. It will act the same as the textarea of a <select>,
  2. I stored all items (to be filtered out) as <li> elements inside a <ul>,
  3. I let ShuffleJS know where these are located.

I have one complication which I think I handled well:

My option items form a very long list and I have two or more of those search-and-select elements in my form with the exact same option items. For example they can be From/To locations in a map, train stops, etc. And so I decided to use only 1 list of option items (the train stops) to be shared among many From/To/etc. search text areas in the same page/form.

This works mostly OK but there are a few problems that I need help with:

  1. The most important problem is with the logic of closing the “dropdown” containing the items to be filtered/selected when the user has made a selection by clicking on an option item in the “dropdown” or user has not made a selection by clicking elsewhere in the form. (By “dropdown” I mean the thing that pops up when I click on the search text area and contains, initially, all the option items, later to be filtered out as I type in that area.). I detect that the user has clicked on somewhere outside the search text area by the onblur event. In which case I close the “dropdown” and clear the search area with no selection by the user. This works fine. However, when the user clicks on an item on the “dropdown” with the intention to select an option item, this is still considered an onblur event for the search textarea and it is handled first, i.e. no selection, text area is cleared. By the time this is handled, there is no “dropdown” and the onclick on the option item fails. I solved this by delaying the onblur for a few milliseconds hoping that the onclick will be handled first, save the user selection and then close the “dropdown”. This works but I suspect there is a race condition looming there and I am not comfortable with this. Is there any better solution?

  2. I would like to make the options dropdown appear not only when I click on the search textarea but also when I use the keyboard to navigate the elements, using the TAB key. the onfocus event does not seem to work.

  3. When I click on a search text area and the “dropdown” appears with all the options, I can not just click on one and select it if I don’t first type something in the text area in order for ShuffleJS to do some filtering, even if I erase my typing afterwards (so as to obtain the unfiltered list of option items). In this situation (i.e. without first typing a search term), clicking on an option does not copy it onto the text area and closing the “dropdown”. Why is that and do you have any hints on how I can fix it?

I don’t use any framework. Just javascript and ShuffleJS (perhaps bootstrap/jquery).

Edit: I have a fiddle for this here : https://jsfiddle.net/bliako/8sm13gx9/11/

The code is below:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
  <meta http-equiv="Pragma" content="no-cache" />
  <meta http-equiv="Expires" content="0" />
  <title>ShuffleJS select dropdown example</title>

  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" />
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/w3-css/4.1.0/w3.css" />

  <script src="https://cdnjs.cloudflare.com/ajax/libs/Shuffle/6.1.0/shuffle.min.js" integrity="sha512-r8mIpk3ypCMwNxH6srRZGbjHQlOFt3Mq2vrZ/iymx6g9JUqTeVavofeX6gbrxAC74X5HmBy5gxyhCm6OiXrM0Q==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
</head>

<body>

  <div class="row h-100 justify-content-center align-items-center">
    <form id="searchform" name="searchform" class="myappsearch" action=''>
      <div class="form-group col">
        <!-- begin the A search text area -->
        <legend><span>Select A <small>(onblur is handled with a timeout after onclick, works)</small>:</span></legend>
        <textarea class="form-control form-control-sm  filter__search js-shuffle-search" id="search-textarea-A" type="search" rows="1" required onkeydown='handle_keydown_in_search_textarea(this, event);' onblur='this.value = ""; mythis = this; setTimeout(function() { close_shuffle_dropdown(mythis); }, 200);'
          onclick='A_clicked(this);'></textarea>
        <!-- this is a lame solution to the dropdown disappearing before the
     onclick event on it is handled: Just act on the onblur after some
     milliseconds so that the onclick has time to be handled first:
                  onblur='close_shuffle_dropdown(this);'

      onblur='this.value = ""; mythis = this; setTimeout(function() { close_shuffle_dropdown(mythis); }, 200);'
-->
      </div>
      <!-- end the A search text area: -->
      <br />

      <div class="form-group col">
        <!-- begin the Select B search text area -->
        <legend><span>Select B <small>(onblur is handled before onclick, problem)</small>:</span></legend>
        <textarea class="form-control form-control-sm  filter__search js-shuffle-search" id="search-textarea-B" type="search" rows="1" required onkeydown='handle_keydown_in_search_textarea(this, event);' onblur='close_shuffle_dropdown(this);' onclick='B_clicked(this);'></textarea>
      </div>
      <!-- end the Select B search text area -->
    </form>
  </div>

  <!-- this UL contains all the available options to select
     Shufflejs displays this in its own way in a "dropdown"
     and then, by typing on ANY of the two search textareas
     you can see the options filtered out.
     Clicking on an option will copy the value of
     that option to the respective search textarea (we have two!)
-->
  <div id='select-options-container' class='custom-select' style='display:none; position:absolute;'>
    <ul id='select-options' style='background:#000000;width:100px;'>
      <li class='select-item' style='color:#ffffff;' id='Orange' onclick='item_has_been_selected(this);'>Orange</li>
      <li class='select-item' style='color:#ffffff;' id='Black' onclick='item_has_been_selected(this);'>Black</li>
      <li class='select-item' style='color:#ffffff;' id='Green123' onclick='item_has_been_selected(this);'>Green123</li>
      <li class='select-item' style='color:#ffffff;' id='Green567' onclick='item_has_been_selected(this);'>Green567</li>
    </ul>
  </div>

  <script>
    var Shuffle = window.Shuffle;
    var currentSearchTextarea = null;

    class ShufflejsFilterer {
      constructor(element) {
        this.element = element;
        this.shuffle = new Shuffle(element, {
          itemSelector: '.select-item',
          //sizer: element.querySelector('.my-sizer-element'),
        });

        this._activeFilters = [];

        // Log events:
        //this.addShuffleEventListeners();

        // create the search filter:
        this.addSearchFilter();
      }

      /**
       * Shuffle uses the CustomEvent constructor to dispatch events. You can listen
       * for them like you normally would (with jQuery for example).
       */
      addShuffleEventListeners() {
        this.shuffle.on(Shuffle.EventType.LAYOUT, (data) => {
          console.log('layout. data:', data);
        });
        this.shuffle.on(Shuffle.EventType.REMOVED, (data) => {
          console.log('removed. data:', data);
        });
      }

      // Advanced filtering
      addSearchFilter() {
        // modified to handle more than 1 search-boxes
        const searchInputs = document.querySelectorAll('.js-shuffle-search');
        if (searchInputs.length == 0) {
          return;
        }
        // no we call this on our
        //    searchInputs.forEach( (asearchInput) => {
        //      asearchInput.addEventListener('keydown', this._handleSearchKeydown.bind(this));
        //    });
      }

      /**
       * Filter the shuffle instance by items with a title that matches the search input.
       * @param {Event} evt Event object.
       */
      _handleSearchKeydown(evt) {
        // this is e.g. the Search textarea A or B
        currentSearchTextarea = evt.target;
        // and this is what the user has typed as a search text
        const searchText = evt.target.value.toLowerCase();
        console.log("searching with this " + searchText);
        this.shuffle.filter((element, shuffle) => {
          // get the text content of the client element which is
          // the text (or anything else via data-attributes)
          // of each element part of the select-items set
          const contents = element.textContent.toLowerCase().trim();
          // and filter it out if it does not contain our search text
          return contents.indexOf(searchText) !== -1;
        });
      }
    } // end class ShufflejsFilterer

    ///////////////////////////////////////
    /// other JS functions
    ///////////////////////////////////////

    function handle_keydown_in_search_textarea(
      caller_obj,
      event
    ) {
      if (event.keyCode == 27) {
        // on ESCape close the shuffle-dropdown and select nothing
        caller_obj.value = '';
        close_shuffle_dropdown(caller_obj);
        return; // nothing to do
      }
      // let ShuffleJS handle it now
      window.myFilterer._handleSearchKeydown(event);
    }

    function close_shuffle_dropdown(
      caller_obj
    ) {
      console.log("close_shuffle_dropdown() : called ...");
      var selObj = document.getElementById('select-options-container');
      //selObj.style.position = 'absolute'; // already set
      selObj.style.display = 'none'; // hide
      // we need to reset the contents of the <ul>
      // if you don't complete (by clicking an item of the <ul>)
      // the <ul> appears truncated but search text is empty.
      // this is what causes it to reset!!!! it is part of _init()
      window.myFilterer.shuffle.filter(window.myFilterer.shuffle.options.group, window.myFilterer.shuffle.options.initialSort);
    }

    function item_has_been_selected(
      item_obj
    ) {
      // once an item has been selected we copy its value to the currently
      // selected textarea:
      currentSearchTextarea.value = item_obj.textContent;
      // and then we close the shuffle dropdown:
      document.getElementById("select-options-container").style.display = "none";
    }

    // Search textarea (A) was clicked, so we show the shuffle dropdown
    function A_clicked(
      obj
    ) {
      const pos = obj.getBoundingClientRect();
      var selObj = document.getElementById('select-options-container');
      /* place the "dropdown" with all the select items in there,
         as formatted by Shufflejs, under the client element
             (search textarea)
      */
      //selObj.style.position = 'absolute'; // already set
      selObj.style.left = pos.left + 'px';
      selObj.style.top = String(pos.bottom + 10) + 'px';
      // and show the "dropdown"
      selObj.style.display = 'inline';
    }
    // Search textarea (B) was clicked, so we show the shuffle dropdown
    // it is the same as the above
    function B_clicked(
      obj
    ) {
      console.log("my val: " + obj.value);
      A_clicked(obj);
    }

    ///////////////////////////////////////
    /// things to do when DOM loaded:
    ///////////////////////////////////////

    document.addEventListener('DOMContentLoaded', () => {
      // create the Shufflejs filterer
      window.myFilterer = new ShufflejsFilterer(document.getElementById('select-options'));

      /*
      window.myFilterer.applyCss({
          INITIAL: {
            position: 'absolute',
            top: 0,
            visibility: 'visible',
            willChange: 'transform',
          }
      });
      */


      ['search-textarea-A', 'search-textarea-B'].forEach((k) => {
        var obj = document.getElementById(k);
        obj.value = '';
      });
    }); // end DOMContentLoaded
  </script>
</body>

</html>

6

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật