Select2 Bootstrap Modal with Modal Scroll
Asked Answered
E

6

9

Update:

I added this:

$("#id_project").select2({dropdownParent: $(".modal-body")});

...and this causes the dropdown to be correctly placed, and the search can be typed into. Unfortunately...this causes the data delivered to be cleared out, and no options are available to be selected.

I have a form that is populated in a Bootstrap modal that looks like this:

<form method="post" action="/dataanalytics/sparc/tasks/create/" class="js-task-create-form">
  <input type="hidden" name="csrfmiddlewaretoken" value="W6cnRq9zPDvUdQOpqceXPVulbWJAMFazRStzwnZhLi8UEqa87SYiRKmMoHAKwmvb">
  <div class="modal-body">
  <div class="form-group">
    <select name="project" data-minimum-input-length="2" class="form-control select2-hidden-accessible" required="" id="id_project" data-autocomplete-light-url="/dataanalytics/projectautocomplete/" data-autocomplete-light-function="select2" tabindex="-1" aria-hidden="true">
       <option value="" selected="">---------</option>
    </select>
    <span class="select2 select2-container select2-container--bootstrap select2-container--focus" dir="ltr" style="width: 505.091px;">
    <span class="selection"><span class="select2-selection select2-selection--single" role="combobox" aria-haspopup="true" aria-expanded="false" tabindex="0" aria-labelledby="select2-id_project-container">
    <span class="select2-selection__rendered" id="select2-id_project-container">
    <span class="select2-selection__placeholder">
    </span>
    </span>
    <span class="select2-selection__arrow" role="presentation"><b role="presentation"></b></span></span></span><span class="dropdown-wrapper" aria-hidden="true">
    </span>
    </span>
  </div>
  <div class="form-group">
    <select name="dataSources" data-minimum-input-length="2" class="form-control select2-hidden-accessible" id="id_dataSources" data-autocomplete-light-url="/dataanalytics/datasourceautocomplete/" data-autocomplete-light-function="select2" multiple="" tabindex="-1" aria-hidden="true">
    </select>
    <span class="select2 select2-container select2-container--bootstrap" dir="ltr" style="width: 505.091px;">
    <span class="selection"><span class="select2-selection select2-selection--multiple" role="combobox" aria-haspopup="true" aria-expanded="false" tabindex="-1">
      <ul class="select2-selection__rendered">
         <li class="select2-search select2-search--inline">
         <input class="select2-search__field" type="search" tabindex="0" autocomplete="off" autocorrect="off" autocapitalize="none" spellcheck="false" role="textbox" aria-autocomplete="list" placeholder="" style="width: 0.75em;">
         </li>
      </ul>
    </span>
    </span>
    <span class="dropdown-wrapper" aria-hidden="true">
    </span>
    </span>
  </div>
</div>
</form>
</div>
</div>

The first form group with the select2 single exhibits the problematic behavior. The second form group with the select2 multiple works as expected. Why is there a disparity here?

JavaScript:

  var loadForm = function () {
    var btn = $(this);
    $.ajax({
      url: btn.attr("data-url"),
      type: 'get',
      dataType: 'json',
      beforeSend: function () {
        $("#modal-task").modal("show");
      },
      success: function (data) {
        $("#modal-task .modal-content").html(data.html_form);
      }
    });
  };

Versions:

  • jQuery: 1.12.1
  • Select2: 4.0.5
  • Bootstrap: 4

Everything works great in Chrome. In Firefox, the Select2 boxes in the modal don't allow typing in the search inputs. This is a known issue (https://select2.org/troubleshooting/common-problems) but none of the known solutions appear to work for me.

To solve this issue, I added this code to set the dropdownParent of each Select2:

$.fn.select2.defaults.set("dropdownParent", $('#modal-task'));

This allows typing in the search inputs. However, my modal is long enough that it requires scrolling. If the modal is scrolled beyond the original position, clicking on a Select2 box causes the choices and the search input to appear above the box at the top of the modal. This makes sense, because all of the Select2 boxes were tied to the modal itself with dropdownParent.

How do I prevent this behavior and force the Select2 choices and search input to appear normally (directly above or below the Select2 depending on where it is in relation to the window edge)? Also, I have Select2 boxes on the page even when a modal is not activated, so affecting all Select2 boxes in this manner is not ideal.

Update: I tried swapping the other code for this line:

$.fn.modal.Constructor.prototype.enforceFocus = function () {};

This has the same effect as doing nothing at all: search inputs don't work in Firefox, but dropdowns are correctly placed.

Update 2: I tried this:

$('#id_project').set("dropdownParent", $('#id_project'));

This caused the dropdown to not appear anywhere, and the options are gone (replaced by ---------).

Update 3: I tried this...

$('#modal-task').css('overflow','visible');

...which resulted in the search input working...but modal scrolling is now broken. In addition, the dropdown is slightly misaligned on the left edge of the Select2 box.

Estellaestelle answered 12/2, 2018 at 21:25 Comment(0)
G
8

just use select2 like this to bypass bootstrap modal bugs:

$('.my-select2').each(function () {
    $(this).select2({
        dropdownParent: $(this).parent(),// fix select2 search input focus bug
    })
})

// fix select2 bootstrap modal scroll bug
$(document).on('select2:close', '.my-select2', function (e) {
    var evt = "scroll.select2"
    $(e.target).parents().off(evt)
    $(window).off(evt)
})
Gujranwala answered 3/3, 2022 at 0:43 Comment(0)
I
3

This works fine for me

$(document).ready(function () {
    $('select.select2:not(.normal)').each(function () {
        $(this).select2({
            dropdownParent: $(this).parent().parent()
        });
    });
});
Indiscretion answered 25/7, 2018 at 9:39 Comment(2)
This might be the answer, but you should at least explain why it works.Spake
Which element should we reach exactly ?Kutchins
D
1

I'm pretty much late, could help someone else. If you have tabindex = -1 on your modal then you may not be able to type. Try to remove it. Worked for me.

Dandy answered 14/5, 2021 at 13:38 Comment(0)
G
1

For me I was using select2 v4 and bootstrap modal. The change event was working with older versions of select2.

I needed to update my event listeners from

selectList.on('change', function(e) {

to select (add item)

selectList.on('select2:select', function(e) {

and unselect (remove item)

selectList.on('select2:unselect', function(e) {

https://select2.org/programmatic-control/events

Geography answered 29/6, 2022 at 20:23 Comment(0)
L
1

First of all in select2 options must have

dropdownParent: $(this).parent(),// That fix weird positioning select2 input

After that we doing little trick when select2 opening event. I realized if modal height smaller than window height when select2 opening no problem for scrolling even after need scrolling.

So we put overflow adjustments on select2 events. When opening select2 set modal body to no overflow, after select2 open set back to default.

    $(document).on("select2:opening", (e) => {
        $('.modal-body').css('overflow', 'visible');
    });
    $(document).on("select2:open", (e) => {
        $('.modal-body').css('overflow', 'auto');
    });

That will fix your problem.

Leges answered 3/2, 2023 at 15:30 Comment(0)
F
0

I rewrited the ORIGINAL Select2 dropdown position function to include the scroll, works for me!

Just copy and paste after sel2 and before you start any sel2.

It only works in sel2 inside modals in bootstrap 5 where my problem was.

i commented where are i changed only, for more info see the original code.

var oldDropdownPosition = $.fn.select2.amd.require('select2/dropdown/attachBody');
        var originalPositionDropdown = oldDropdownPosition.prototype._positionDropdown;
        oldDropdownPosition.prototype._positionDropdown = function() {
            if (this.$dropdownParent && this.$dropdownParent.closest && this.$dropdownParent.closest('.modal')){
                var $window = $(window);
                var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
                var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
                var newDirection = null;
                var offset = this.$container.offset();
                offset.bottom = offset.top + this.$container.outerHeight(false);
                var container = {
                    height: this.$container.outerHeight(false)
                };
                container.top = offset.top;
                container.bottom = offset.top + container.height;
                var dropdown = {
                    height: this.$dropdown.outerHeight(false)
                };
                var viewport = {
                    top: $window.scrollTop(),
                    bottom: $window.scrollTop() + $window.height()
                };
                var enoughRoomBelow = viewport.bottom > (container.bottom + dropdown.height);
                var enoughRoomAbove = viewport.top < (container.top - dropdown.height);
                var css = {
                    left: offset.left,
                    top: container.bottom
                };
                // Adjust position based on scrolling
                css.top = css.top - $window.scrollTop();
                if (!isCurrentlyAbove && !isCurrentlyBelow) {
                    newDirection = 'below';
                }
                if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
                    newDirection = 'above';
                } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
                    newDirection = 'below';
                }
                if (newDirection == 'above' ||
                    (isCurrentlyAbove && newDirection !== 'below')) {
                    css.top = container.top - dropdown.height;
                }
                if (newDirection != null) {
                    this.$dropdown
                        .removeClass('select2-dropdown--below select2-dropdown--above')
                        .addClass('select2-dropdown--' + newDirection);
                    this.$container
                        .removeClass('select2-container--below select2-container--above')
                        .addClass('select2-container--' + newDirection);
                }
                this.$dropdownContainer.css(css);
            } else {
                // Call the original function if not inside a modal
                originalPositionDropdown.apply(this, arguments);
            }
        };
Fina answered 17/5, 2024 at 12:21 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Been

© 2022 - 2025 — McMap. All rights reserved.