防止select2将下拉菜单向上翻转

30

标题所述,有没有一种方法可以强制Select2始终创建下拉框而不是向上弹出框?

还有一些JavaScript代码,当您滚动到下拉框上方时,它会导致翻转,并添加一个名为“select2-drop-above”的新CSS类。

编辑:我应该说明我是通过select2-rails引入库的。我希望有一种方法可以避免直接拉取整个select2库并直接编辑select2.js文件。

7个回答

37

由于修改源代码不是一个选项,而添加钩子到select2:open事件也不太优雅,特别是当您在同一页上有多个Select2实例时,我为Select2插件编写了一个小扩展。

我的实现受到了来自插件存储库的PR的启发(https://github.com/select2/select2/pull/4618),该PR尚未合并。

基本上,以下代码覆盖了处理下拉列表位置的原始插件函数,并添加了一个新选项(dropdownPosition)以强制将下拉列表位置设置为上方或下方。

新的dropdownPosition选项可以使用以下值: - below - 下拉列表始终显示在输入框底部; - above - 下拉列表始终显示在输入框顶部; - auto(默认值)- 使用旧行为。

只需在select2.js文件之后插入以下代码:

(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);

使用以下方式初始化插件:

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

在这里查看示例代码: https://jsfiddle.net/byxj73ov/

Github存储库: https://github.com/andreivictor/select2-dropdownPosition


更新于2019年12月30日

使用最新的Select2版本 (v4.0.12) 查看示例代码: https://jsfiddle.net/g4maj9ox/


1
谢谢!正在使用select2 4.0版本工作! - Dave
1
@KaranSharma,我已经使用select2版本4.0.7创建了一个fiddle:https://jsfiddle.net/51sxfa0L/,它似乎工作正常。你能否提供更多细节或提供一个最小可重现的错误示例? - andreivictor
啊,我的错,我添加了两个select2库。一个来自vendor.min.js,我现在找到了它,另一个来自cdn链接。我在jsfiddle上再试了一下,发现了与之相关的问题。谢谢你的建议 :) 我对此已经不抱希望了。 - Karan Sharma
1
非常感谢,这是唯一干净的解决方案来解决这个问题。这确实应该成为select2的一部分... - aprovent
谢谢!正在使用select2 4.0版本工作! - Offir
谢谢,这正是我在寻找的。 - Kyun

30

你可以直接编辑select2.js


它所说的是

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

只需更改它为

enoughRoomBelow = true,
enoughRoomAbove = false,

13
这意味着当你未来想要升级Select2时,需要牢记这一点。因此,请注意这一点。 - MarkoHiel
2
你绝不能修改源代码,否则以后会出现问题。 - cmfolio
不幸的是,这对我来说是唯一修复它的方法(使用ajax查询和需要在选择时关闭)。已经创建了一个PR来解决这个问题,但尚未合并: https://github.com/select2/select2/pull/4560 - Clément Poissonnier
对我不起作用 :( 另一种方式可以,但这个不行。可能是什么原因? - senty
解决方案正在运作,但我强烈同意@cmfolio的观点。相反,有人应该使用“enoughRoomAbove: true/false”作为插件配置选项来提出拉取请求。让我抓住这个机会:)。谢谢。 - Kushal Jayswal
显示剩余2条评论

9
您可以通过覆盖CSS来实现这一点,如下所示:
.select-dropdown {
  position: static;
}
.select-dropdown .select-dropdown--above {
      margin-top: 336px;
}

尝试使用4.0版本,完美运行且最简单的解决方案! - SBT23434

4

我曾经找到了一个更简单/更快的解决方案:

        $("select").select2({

            // Options

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

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

        });

这很简单,您只需要在打开事件(select2:open)中将 .select2-dropdown--above 更改为 .select2-dropdown--below

它只能在该事件下起作用,还有许多其他方法可以在选择框打开时更改类。

注意:如果您尝试使用jquery填充选择框,则无法使用此方法。


无法正常工作,因为select2添加了一些带有top值的内联CSS。 - Ben
不知道如何在相关元素上使用内联顶部值。 - Ben
我试图做这样的事情时弄伤了手指,我不确定它是如何工作的,也没有理论支持。我是通过尝试实现的。 - user4542733
不是理论,我已经尝试过你的方法了。也许看一下最新版本吧。它在打开时添加了一个顶部内联值;所以仅仅切换类可能不够。 - Ben

3
修改插件并不是首选方案,如你所说。我曾遇到类似问题,但无法找到使用select2选项来强制下拉框保持在下方的方法。最终我的解决方案如下:
$("#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);
        }
    });

这段代码用于确定是否有足够的空间将下拉菜单放置在底部,如果没有,则通过向页面上的某个元素添加 margin-bottom 来创建它。然后,它会滚动到 select2 的正上方,以避免下拉菜单翻转。

我不确定你的意思,但在Chrome中对我来说它运行良好。 - shanabus
2
我使用的版本是4.0,而这个还是3。不过我已经修复了。http://jsfiddle.net/brunodd/jEADR/2009 - brunodd

2

更新自[shanabus]的回答

jQuery("#line_item").select2({
            formatResult: Invoice.formatLineItem
        })
        .on('select2-open', function() {
            // however much room you determine you need to prevent jumping
            var requireHeight = $("#select2-drop").height()+10;
            var viewportBottom = $(window).height() - $("#line_item").offset().top;

            // 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: $("#select2-drop").offset().top - 10
                }, 1000);
            }
        });

1
什么是$(".aLwrElmntOrCntntWrppr")? - JaskeyLam
5
"aLwrElmntOrCntntWrppr" 是一个非常糟糕的名字。 - Shai
@Jaskey 我猜意思是“一个较低的元素或内容包装器”。 - MattOpen

1
看起来在CSS中强制将底部值设为自动也可以解决问题。我使用了以下代码,它对我来说效果还不错。
.my_select_picker_trigger.dropup .dropdown-menu {
    bottom: auto !important;
}

这是一个很好的答案,适用于所有下拉菜单。使用静态位置会在打开下拉菜单时添加额外的空白,这并不理想。 - Pseudo-Hippy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接