Prevent select2 from flipping the dropdown upward

41,951

Solution 1

Modifying the plugin is not preferred as you mention. I had a similar issue and couldn't find an way to use select2 options to force the dropdown to stay below. The solution I ended up with is the following:

$("#mySelect2").select2({ ...options... })
    .on('select2-open', function() {

        // however much room you determine you need to prevent jumping
        var requireHeight = 600;
        var viewportBottom = $(window).scrollTop() + $(window).height();

        // figure out if we need to make changes
        if (viewportBottom < requireHeight) 
        {           
            // determine how much padding we should add (via marginBottom)
            var marginBottom = requireHeight - viewportBottom;

            // adding padding so we can scroll down
            $(".aLwrElmntOrCntntWrppr").css("marginBottom", marginBottom + "px");

            // animate to just above the select2, now with plenty of room below
            $('html, body').animate({
                scrollTop: $("#mySelect2").offset().top - 10
            }, 1000);
        }
    });

This code determines if there is enough room to place the dropdown at the bottom and if not, creates it by adding margin-bottom to some element on the page. It then scrolls to just above the select2 so that the dropdown won't flip.

Solution 2

Since modifying the source code is not an option and adding a hook to the select2:open event is not very elegant, especially when you have multiple select2 instances in the same page, I have written a small extension for the Select2 plugin.

My implementation is inspired by a PR from the plugin's repository (https://github.com/select2/select2/pull/4618) that is not yet merged.

Basically, the following code overrides the original plugin function that handles the dropdown positioning and adds a new option (dropdownPosition) to force the dropdown positioning above/below.

The new dropdownPosition option can take the following values: - below - the dropdown is always displayed at the bottom of the input; - above - the dropdown is always displayed at the top of the input; - auto (default) - it uses the old behavior.

Just insert the following code after select2.js file:

(function($) {

  var Defaults = $.fn.select2.amd.require('select2/defaults');

  $.extend(Defaults.defaults, {
    dropdownPosition: 'auto'
  });

  var AttachBody = $.fn.select2.amd.require('select2/dropdown/attachBody');

  var _positionDropdown = AttachBody.prototype._positionDropdown;

  AttachBody.prototype._positionDropdown = function() {

    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 enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
    var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);

    var css = {
      left: offset.left,
      top: container.bottom
    };

    // Determine what the parent element is to use for calciulating the offset
    var $offsetParent = this.$dropdownParent;

    // For statically positoned elements, we need to get the element
    // that is determining the offset
    if ($offsetParent.css('position') === 'static') {
      $offsetParent = $offsetParent.offsetParent();
    }

    var parentOffset = $offsetParent.offset();

    css.top -= parentOffset.top
    css.left -= parentOffset.left;

    var dropdownPositionOption = this.options.get('dropdownPosition');

    if (dropdownPositionOption === 'above' || dropdownPositionOption === 'below') {
      newDirection = dropdownPositionOption;
    } else {

      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 - parentOffset.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);

  };

})(window.jQuery);

The initialize the plugin with as follows:

$(document).ready(function() {
  $(".select-el").select2({
    dropdownPosition: 'below'
  });
});

Fiddle here: https://jsfiddle.net/byxj73ov/

Github repository: https://github.com/andreivictor/select2-dropdownPosition


UPDATE December 30, 2019

Fiddle with the latest Select2 Version (v4.0.12): https://jsfiddle.net/g4maj9ox/

Solution 3

You can just edit select2.js


Where it says

enoughRoomBelow = dropTop + dropHeight <= viewportBottom,
enoughRoomAbove = (offset.top - dropHeight) >= this.body().scrollTop(),

just change it to

enoughRoomBelow = true,
enoughRoomAbove = false,

Solution 4

You can do it by overwriting CSS like so:

.select-dropdown {
  position: static;
}
.select-dropdown .select-dropdown--above {
      margin-top: 336px;
}

Solution 5

I used to find an easier/faster solution for that:

        $("select").select2({

            // Options

        }).on('select2:open',function(){

            $('.select2-dropdown--above').attr('id','fix');
            $('#fix').removeClass('select2-dropdown--above');
            $('#fix').addClass('select2-dropdown--below');

        });

It's simple, you just change the .select2-dropdown--above to .select2-dropdown--below in the opening event (select2:open).

It will only works under the event, and there could be many other ways to perform it just changing classes when the select is opened.

ps. It won't work if you try to populate your select using jquery.

Share:
41,951

Related videos on Youtube

kjb
Author by

kjb

Roaming nomad with a penchant for adventure. Straddling the line between outdoorsy luddite and urban programming geek.

Updated on March 24, 2021

Comments

  • kjb
    kjb about 3 years

    As per title, is there a way to force select2 to always create a dropdown instead of a drop-up?

    There also appears to be some javascript that is either causing the flip when you scroll above the dropdown, adding a new CSS class "select2-drop-above", or both.

    EDIT: I should've specified that I'm pulling the library in via select2-rails. I'm hoping there is a way around this that doesn't involve pulling the whole select2 lib in myself and editing the select2.js file directly.

  • MarkoHiel
    MarkoHiel about 10 years
    But that means that you have keep that in mind, when you want to upgrade select2 in the future. So be careful with this.
  • shanabus
    shanabus over 8 years
    I'm not sure what you mean, it works ok for me in Chrome
  • brunodd
    brunodd over 8 years
    The version that I am using is 4.0 and this one is still 3. I've already fixed anyway. jsfiddle.net/brunodd/jEADR/2009
  • cmfolio
    cmfolio over 8 years
    You should never be modifying the source script or you WILL have problems down the line.
  • Ben
    Ben over 8 years
    does not work as select2 adds some inline css with a top value
  • Ben
    Ben over 8 years
    Dunno how possible with an inline top value on the concerned element
  • Admin
    Admin over 8 years
    I broke my fingers trying to do such thing, I am not sure how did it work, none theory for that. I achieved it by tries.
  • Ben
    Ben over 8 years
    it's not theory i've tried yours. maybe look at the latest release. it adds a top inline value when it opens; so just switching classes can't be enough
  • JaskeyLam
    JaskeyLam about 8 years
    what is $(".aLwrElmntOrCntntWrppr")
  • Clément Poissonnier
    Clément Poissonnier over 7 years
    Unfortunatly, that's the only way to fix it for me (with ajax querying and the need to close on select). A PR has been created to fix this issue, but is not merged yet : github.com/select2/select2/pull/4560
  • senty
    senty over 7 years
    doesn't work for me :( The other way around works but not this. What may be the reason?
  • Kushal Jayswal
    Kushal Jayswal over 6 years
    Solution is working but I strongly agree with @cmfolio. Instead someone should raise a pull request with enoughRoomAbove: true/false as plugin configuration option. Let me grab the opportunity :). Thanks.
  • Dave
    Dave over 5 years
    Thanks! Working on select2 version 4.0!
  • Shai
    Shai over 5 years
    aLwrElmntOrCntntWrppr is a horrible, horrible name.
  • Fabian von Ellerts
    Fabian von Ellerts about 5 years
    Are there any better/modern select libraries? After taking a look into the source code and after this has not been merged for 3 years...
  • andreivictor
    andreivictor over 4 years
    @KaranSharma, I've created a fiddle with select2 version 4.0.7: jsfiddle.net/51sxfa0L, it seems to work well. Can you give more details or provide a minimal reproducible example of the error you encounter?
  • Karan Sharma
    Karan Sharma over 4 years
    Ah, My mistake, I was adding two select2 libraries. One coming from vendor.min.js which i found now and other was from cdn link. I tried on jsfiddle again and found the issue related to it. Thanks for suggestion :) I left hope on it.
  • MattOpen
    MattOpen over 4 years
    @Jaskey I guess the meaning was "a lower element or content wrapper"
  • SBT23434
    SBT23434 over 4 years
    Trying this with 4.0, works perfectly and the most simple solution!
  • aprovent
    aprovent almost 4 years
    Thank you very much, this is the only clean solution to resolve this issue.This shoud really be part of select2...
  • Offir
    Offir over 3 years
    Thanks! Working on select2 version 4.0!