How to prevent multi-select fields from blocking other form fields on mobile when keyboard dismisses? Answered

Post author
Daniel Haas

Hi! I'm using memberstack script 109 for the multi select search fields. I have multiple on them on one page (see ss). Everything works great, except for the fact that on mobile in order to hide the select field you need to physically click off of the select field in order to hide it. The native "done" button that appears on the iphone keyboard or the down arrow at the bottom of androids does not hide the select field. It ends up blocking my other fields and creates a big mess. Is there a fix to this so that the "done" button on the keyboard actually hides the field? Please see the screenshots below--they probably explain my issue better than I do haha.

Comments

4 comments

  • Comment author
    A J
    • Official comment

    Daniel Haas, yes that's possible. I have modified the above code as follows to include 10 items cap limit, if the user selects more than 10, they will be alerted for the same:

    <!-- 💙 MEMBERSCRIPT #109 v0.1 💙 - CUSTOM MULTI SELECT -->
    <script>
      $(document).ready(function() {
        $('[ms-code-select-wrapper]').each(function() {
          const $wrapper = $(this);
          const isMulti = $wrapper.attr('ms-code-select-wrapper') === 'multi';
          const $input = $wrapper.find('[ms-code-select="input"]');
          const $list = $wrapper.find('[ms-code-select="list"]');
          const $selectedWrapper = $wrapper.find('[ms-code-select="selected-wrapper"]');
          const $emptyState = $wrapper.find('[ms-code-select="empty-state"]');
          const options = $input.attr('ms-code-select-options').split(',').map(opt => opt.trim());
    
          let selectedOptions = [];
          let highlightedIndex = -1;
    
          const $templateSelectedTag = $selectedWrapper.find('[ms-code-select="tag"]');
          const templateSelectedTagHTML = $templateSelectedTag.prop('outerHTML');
          $templateSelectedTag.remove();
    
          const $templateNewTag = $list.find('[ms-code-select="tag-name-new"]');
          const templateNewTagHTML = $templateNewTag.prop('outerHTML');
          $templateNewTag.remove();
    
          function createSelectedTag(value) {
            const $newTag = $(templateSelectedTagHTML);
            $newTag.find('[ms-code-select="tag-name-selected"]').text(value);
            $newTag.find('[ms-code-select="tag-close"]').on('click', function(e) {
              e.stopPropagation();
              removeTag(value);
            });
            return $newTag;
          }
    
          function addTag(value) {
            if (selectedOptions.length < 10 && !selectedOptions.includes(value) && options.includes(value)) {
              selectedOptions.push(value);
              $selectedWrapper.append(createSelectedTag(value));
              updateInput();
              filterOptions();
            } else if (selectedOptions.length >= 10) {
              alert('Maximum selection limit reached of 10 items.');
            }
          }
    
          function removeTag(value) {
            selectedOptions = selectedOptions.filter(option => option !== value);
            $selectedWrapper.find(`[ms-code-select="tag-name-selected"]:contains("${value}")`).closest('[ms-code-select="tag"]').remove();
            updateInput();
             if (isMulti && selectedOptions.length > 0) {
              $input.val($input.val() + ', ');
            }   
            filterOptions();
            toggleList(false);
            cleanInput();
          }
    
          function updateInput() {
            $input.val(selectedOptions.join(', '));
          }
    
          function toggleList(show) {
            $list.toggle(show);
          }
    
          function createOptionElement(value) {
            const $option = $(templateNewTagHTML);
            $option.text(value);
            $option.on('click', function() {
              selectOption(value);
            });
            return $option;
          }
    
          function selectOption(value) {
            if (isMulti) {
              addTag(value);
              $input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
            } else {
              selectedOptions = [value];
              $selectedWrapper.empty().append(createSelectedTag(value));
              updateInput();
              toggleList(false);
            }
            filterOptions();
            toggleList(false);
            cleanInput();
          }
    
          function filterOptions() {
            const inputValue = $input.val();
            const searchTerm = isMulti ? inputValue.split(',').pop().trim() : inputValue.trim();
            let visibleOptionsCount = 0;
    
            $list.find('[ms-code-select="tag-name-new"]').each(function() {
              const $option = $(this);
              const optionText = $option.text().toLowerCase();
              const matches = optionText.includes(searchTerm.toLowerCase());
              const isSelected = selectedOptions.includes($option.text());
              $option.toggle(matches && !isSelected);
              if (matches && !isSelected) visibleOptionsCount++;
            });
    
            $emptyState.toggle(visibleOptionsCount === 0 && searchTerm !== '');
            highlightedIndex = -1;
            updateHighlight();
          }
    
          function cleanInput() {
            const inputValues = $input.val().split(',').map(v => v.trim()).filter(v => v);
            const validValues = inputValues.filter(v => options.includes(v));
            selectedOptions = validValues;
            $selectedWrapper.empty();
            selectedOptions.forEach(value => $selectedWrapper.append(createSelectedTag(value)));
            updateInput();
            filterOptions();
          }
    
          function handleInputChange() {
            const inputValue = $input.val();
            const inputValues = inputValue.split(',').map(v => v.trim());
            const lastValue = inputValues[inputValues.length - 1];
    
            if (inputValue.endsWith(',') || inputValue.endsWith(', ')) {
              inputValues.pop();
              const newValidValues = inputValues.filter(v => options.includes(v) && !selectedOptions.includes(v));
              newValidValues.forEach(addTag);
              $input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
            } else if (options.includes(lastValue) && !selectedOptions.includes(lastValue)) {
              addTag(lastValue);
              $input.val(selectedOptions.join(', ') + ', ');
            }
    
            filterOptions();
          }
    
          function initializeWithValue() {
            const initialValue = $input.val();
            if (initialValue) {
              const initialValues = initialValue.split(',').map(v => v.trim());
              initialValues.forEach(value => {
                if (options.includes(value)) {
                  addTag(value);
                }
              });
              updateInput();
              filterOptions();
            }
          }
    
          function updateHighlight() {
            $list.find('[ms-code-select="tag-name-new"]').removeClass('highlighted').css('background-color', '');
            if (highlightedIndex >= 0) {
              $list.find('[ms-code-select="tag-name-new"]:visible').eq(highlightedIndex)
                .addClass('highlighted')
                .css('background-color', '#e0e0e0');
            }
          }
    
          function handleKeyDown(e) {
            const visibleOptions = $list.find('[ms-code-select="tag-name-new"]:visible');
            const optionCount = visibleOptions.length;
    
            switch (e.key) {
              case 'ArrowDown':
                e.preventDefault();
                highlightedIndex = (highlightedIndex + 1) % optionCount;
                updateHighlight();
                break;
              case 'ArrowUp':
                e.preventDefault();
                highlightedIndex = (highlightedIndex - 1 + optionCount) % optionCount;
                updateHighlight();
                break;
              case 'Enter':
                e.preventDefault();
                if (highlightedIndex >= 0) {
                  const selectedValue = visibleOptions.eq(highlightedIndex).text();
                  selectOption(selectedValue);
                }
                break;
            }
          }
    
          $.each(options, function(i, option) {
            $list.append(createOptionElement(option));
          });
    
          $input.on('focus', function() {
            toggleList(true);
            if (isMulti) {
              const currentVal = $input.val().trim();
              if (currentVal !== '' && !currentVal.endsWith(',')) {
                $input.val(currentVal + ', ');
              }
              this.selectionStart = this.selectionEnd = this.value.length;
            }
            filterOptions();
          });
    
          $input.on('click', function(e) {
            e.preventDefault();
            this.selectionStart = this.selectionEnd = this.value.length;
          });
    
          $input.on('blur', function() {
            setTimeout(function() {
              if (!$list.is(':hover')) {
                toggleList(false);
                cleanInput();
              }
            }, 100);
          });
          
    
          $input.on('input', handleInputChange);
          $input.on('keydown', handleKeyDown);
    
          $list.on('mouseenter', '[ms-code-select="tag-name-new"]', function() {
            $(this).css('background-color', '#e0e0e0');
          });
    
          $list.on('mouseleave', '[ms-code-select="tag-name-new"]', function() {
            if (!$(this).hasClass('highlighted')) {
              $(this).css('background-color', '');
            }
          });
    
          initializeWithValue();
          toggleList(false);
        });
      });
    </script> 
    

    Hope this helps.

  • Comment author
    A J

    Hey Daniel Haas its a bit tricky to set this up based on done / arrow button on mobile keyboards. But I was trying to create a workaround to simulate how better select fields close the dropdown for each selection / removal made.

    So, I modified the 109 script as follows:

    <!-- 💙 MEMBERSCRIPT #109 v0.1 💙 - CUSTOM MULTI SELECT -->
    <script>
      $(document).ready(function() {
        $('[ms-code-select-wrapper]').each(function() {
          const $wrapper = $(this);
          const isMulti = $wrapper.attr('ms-code-select-wrapper') === 'multi';
          const $input = $wrapper.find('[ms-code-select="input"]');
          const $list = $wrapper.find('[ms-code-select="list"]');
          const $selectedWrapper = $wrapper.find('[ms-code-select="selected-wrapper"]');
          const $emptyState = $wrapper.find('[ms-code-select="empty-state"]');
          const options = $input.attr('ms-code-select-options').split(',').map(opt => opt.trim());
    
          let selectedOptions = [];
          let highlightedIndex = -1;
    
          const $templateSelectedTag = $selectedWrapper.find('[ms-code-select="tag"]');
          const templateSelectedTagHTML = $templateSelectedTag.prop('outerHTML');
          $templateSelectedTag.remove();
    
          const $templateNewTag = $list.find('[ms-code-select="tag-name-new"]');
          const templateNewTagHTML = $templateNewTag.prop('outerHTML');
          $templateNewTag.remove();
    
          function createSelectedTag(value) {
            const $newTag = $(templateSelectedTagHTML);
            $newTag.find('[ms-code-select="tag-name-selected"]').text(value);
            $newTag.find('[ms-code-select="tag-close"]').on('click', function(e) {
              e.stopPropagation();
              removeTag(value);
            });
            return $newTag;
          }
    
          function addTag(value) {
            if (!selectedOptions.includes(value) && options.includes(value)) {
              selectedOptions.push(value);
              $selectedWrapper.append(createSelectedTag(value));
              updateInput();
              filterOptions();
            }
          }
    
          function removeTag(value) {
            selectedOptions = selectedOptions.filter(option => option !== value);
            $selectedWrapper.find(`[ms-code-select="tag-name-selected"]:contains("${value}")`).closest('[ms-code-select="tag"]').remove();
            updateInput();
             if (isMulti && selectedOptions.length > 0) {
              $input.val($input.val() + ', ');
            }   
            filterOptions();
            toggleList(false);
            cleanInput();
          }
    
          function updateInput() {
            $input.val(selectedOptions.join(', '));
          }
    
          function toggleList(show) {
            $list.toggle(show);
          }
    
          function createOptionElement(value) {
            const $option = $(templateNewTagHTML);
            $option.text(value);
            $option.on('click', function() {
              selectOption(value);
            });
            return $option;
          }
    
          function selectOption(value) {
            if (isMulti) {
              addTag(value);
              $input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
            } else {
              selectedOptions = [value];
              $selectedWrapper.empty().append(createSelectedTag(value));
              updateInput();
              toggleList(false);
            }
            filterOptions();
            toggleList(false);
            cleanInput();
          }
    
          function filterOptions() {
            const inputValue = $input.val();
            const searchTerm = isMulti ? inputValue.split(',').pop().trim() : inputValue.trim();
            let visibleOptionsCount = 0;
    
            $list.find('[ms-code-select="tag-name-new"]').each(function() {
              const $option = $(this);
              const optionText = $option.text().toLowerCase();
              const matches = optionText.includes(searchTerm.toLowerCase());
              const isSelected = selectedOptions.includes($option.text());
              $option.toggle(matches && !isSelected);
              if (matches && !isSelected) visibleOptionsCount++;
            });
    
            $emptyState.toggle(visibleOptionsCount === 0 && searchTerm !== '');
            highlightedIndex = -1;
            updateHighlight();
          }
    
          function cleanInput() {
            const inputValues = $input.val().split(',').map(v => v.trim()).filter(v => v);
            const validValues = inputValues.filter(v => options.includes(v));
            selectedOptions = validValues;
            $selectedWrapper.empty();
            selectedOptions.forEach(value => $selectedWrapper.append(createSelectedTag(value)));
            updateInput();
            filterOptions();
          }
    
          function handleInputChange() {
            const inputValue = $input.val();
            const inputValues = inputValue.split(',').map(v => v.trim());
            const lastValue = inputValues[inputValues.length - 1];
    
            if (inputValue.endsWith(',') || inputValue.endsWith(', ')) {
              inputValues.pop();
              const newValidValues = inputValues.filter(v => options.includes(v) && !selectedOptions.includes(v));
              newValidValues.forEach(addTag);
              $input.val(selectedOptions.join(', ') + (selectedOptions.length > 0 ? ', ' : ''));
            } else if (options.includes(lastValue) && !selectedOptions.includes(lastValue)) {
              addTag(lastValue);
              $input.val(selectedOptions.join(', ') + ', ');
            }
    
            filterOptions();
          }
    
          function initializeWithValue() {
            const initialValue = $input.val();
            if (initialValue) {
              const initialValues = initialValue.split(',').map(v => v.trim());
              initialValues.forEach(value => {
                if (options.includes(value)) {
                  addTag(value);
                }
              });
              updateInput();
              filterOptions();
            }
          }
    
          function updateHighlight() {
            $list.find('[ms-code-select="tag-name-new"]').removeClass('highlighted').css('background-color', '');
            if (highlightedIndex >= 0) {
              $list.find('[ms-code-select="tag-name-new"]:visible').eq(highlightedIndex)
                .addClass('highlighted')
                .css('background-color', '#e0e0e0');
            }
          }
    
          function handleKeyDown(e) {
            const visibleOptions = $list.find('[ms-code-select="tag-name-new"]:visible');
            const optionCount = visibleOptions.length;
    
            switch (e.key) {
              case 'ArrowDown':
                e.preventDefault();
                highlightedIndex = (highlightedIndex + 1) % optionCount;
                updateHighlight();
                break;
              case 'ArrowUp':
                e.preventDefault();
                highlightedIndex = (highlightedIndex - 1 + optionCount) % optionCount;
                updateHighlight();
                break;
              case 'Enter':
                e.preventDefault();
                if (highlightedIndex >= 0) {
                  const selectedValue = visibleOptions.eq(highlightedIndex).text();
                  selectOption(selectedValue);
                }
                break;
            }
          }
    
          $.each(options, function(i, option) {
            $list.append(createOptionElement(option));
          });
    
          $input.on('focus', function() {
            toggleList(true);
            if (isMulti) {
              const currentVal = $input.val().trim();
              if (currentVal !== '' && !currentVal.endsWith(',')) {
                $input.val(currentVal + ', ');
              }
              this.selectionStart = this.selectionEnd = this.value.length;
            }
            filterOptions();
          });
    
          $input.on('click', function(e) {
            e.preventDefault();
            this.selectionStart = this.selectionEnd = this.value.length;
          });
    
          $input.on('blur', function() {
            setTimeout(function() {
              if (!$list.is(':hover')) {
                toggleList(false);
                cleanInput();
              }
            }, 100);
          });
          
    
          $input.on('input', handleInputChange);
          $input.on('keydown', handleKeyDown);
    
          $list.on('mouseenter', '[ms-code-select="tag-name-new"]', function() {
            $(this).css('background-color', '#e0e0e0');
          });
    
          $list.on('mouseleave', '[ms-code-select="tag-name-new"]', function() {
            if (!$(this).hasClass('highlighted')) {
              $(this).css('background-color', '');
            }
          });
    
          initializeWithValue();
          toggleList(false);
        });
      });
    </script> 
    

    You can test this out in a dummy page with the multi-select field if you want to test how it works. Basically, when a user selects / deselects an option, the dropdown closes itself. Let me know if this helps your use-case.

    0
  • Comment author
    Daniel Haas

    Hi A J Always great to see your helpful responses. This isn't exactly what I was thinking of, but now that I think of it, it is definitely the easiest solution and I can confirm it works so much better on mobile now.
    I'll be using this. Thanks!

    Sorry, one more thing. Is there a way to add a maximum number of selections? I have hundreds of options to choose from, and to prevent people from overloading my api I want to cap it at 10 selections for each select field.

    0
  • Comment author
    Daniel Haas

    A J as always, this is literally perfect. Thank you so much!!

    0

Please sign in to leave a comment.