Prevent select2 from flipping the dropdown upward
You can do it by overwriting CSS like so:
.select-dropdown {
position: static;
}
.select-dropdown .select-dropdown--above {
margin-top: 336px;
}
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/
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,