Skip to content

Commit ce38cd5

Browse files
committed
Allow the dropdown to point up
Up until now, the container could only have a dropdown that was displayed below the container. While this worked well, there are some cases where the dropdown should be displayed above the container. Most notably, this is when the conatainer is displayed towards the bottom of the page, and displaying the dropdown would cause the page to be extended. Because we close the dropdown when the page is scrolled, the user would not be able to access any options that were displayed outside of the viewpoint. Because of the order that events are fired, we attach the handlers for repositioning the dropdown after results are displayed when the container is opened for the first time. This allows it to be registered after the results container registers their events, so the dropdown is repositioned after the results have been displayed. The logic for determining the direction that the dropdown will be shown in is very similar to the positioning code used in Select2 3.x. Unlike previous versions of Select2, a class is used to indicate the direction of the dropdown, even if it is displaying below the container. The themes provided with Select2 have been updated to correctly render the dropdown in both directions.
1 parent fda973c commit ce38cd5

14 files changed

Lines changed: 463 additions & 42 deletions

File tree

dist/css/select2.css

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,17 @@
6666
cursor: pointer; }
6767

6868
.select2-container--open .select2-dropdown {
69+
left: 0; }
70+
71+
.select2-container--open .select2-dropdown--above {
72+
border-bottom: none;
73+
border-bottom-left-radius: 0;
74+
border-bottom-right-radius: 0; }
75+
76+
.select2-container--open .select2-dropdown--below {
6977
border-top: none;
7078
border-top-left-radius: 0;
71-
border-top-right-radius: 0;
72-
left: 0; }
79+
border-top-right-radius: 0; }
7380

7481
.select2-search--dropdown {
7582
display: block;
@@ -143,7 +150,10 @@
143150
margin-right: 2px; }
144151
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
145152
color: #333; }
146-
.select2-container--default.select2-container--open .select2-selection--single, .select2-container--default.select2-container--open .select2-selection--multiple {
153+
.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
154+
border-top-left-radius: 0;
155+
border-top-right-radius: 0; }
156+
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
147157
border-bottom-left-radius: 0;
148158
border-bottom-right-radius: 0; }
149159
.select2-container--default .select2-search--dropdown .select2-search__field {
@@ -218,7 +228,23 @@
218228
top: 50%;
219229
width: 0; }
220230
.select2-container--classic.select2-container--open .select2-selection--single {
221-
border: 1px solid #5897fb;
231+
border: 1px solid #5897fb; }
232+
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
233+
background: transparent;
234+
border: none; }
235+
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
236+
border-color: transparent transparent #888 transparent;
237+
border-width: 0 4px 5px 4px; }
238+
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
239+
border-top: none;
240+
border-top-left-radius: 0;
241+
border-top-right-radius: 0;
242+
background-image: -webkit-linear-gradient(top, #ffffff 0%, #eee 50%);
243+
background-image: -o-linear-gradient(top, #ffffff 0%, #eee 50%);
244+
background-image: linear-gradient(to bottom, #ffffff 0%, #eee 50%);
245+
background-repeat: repeat-x;
246+
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#eee', GradientType=0); }
247+
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
222248
border-bottom: none;
223249
border-bottom-left-radius: 0;
224250
border-bottom-right-radius: 0;
@@ -227,12 +253,6 @@
227253
background-image: linear-gradient(to bottom, #eee 50%, #ffffff 100%);
228254
background-repeat: repeat-x;
229255
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eee', endColorstr='#ffffff', GradientType=0); }
230-
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
231-
background: transparent;
232-
border: none; }
233-
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
234-
border-color: transparent transparent #888 transparent;
235-
border-width: 0 4px 5px 4px; }
236256
.select2-container--classic .select2-selection--multiple {
237257
background-color: white;
238258
border: 1px solid #aaa;
@@ -263,7 +283,12 @@
263283
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
264284
color: #555; }
265285
.select2-container--classic.select2-container--open .select2-selection--multiple {
266-
border: 1px solid #5897fb;
286+
border: 1px solid #5897fb; }
287+
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
288+
border-top: none;
289+
border-top-left-radius: 0;
290+
border-top-right-radius: 0; }
291+
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
267292
border-bottom: none;
268293
border-bottom-left-radius: 0;
269294
border-bottom-right-radius: 0; }
@@ -274,6 +299,10 @@
274299
outline: 0; }
275300
.select2-container--classic .select2-dropdown {
276301
background-color: white;
302+
border: 1px solid transparent; }
303+
.select2-container--classic .select2-dropdown--above {
304+
border-bottom: none; }
305+
.select2-container--classic .select2-dropdown--below {
277306
border-top: none; }
278307
.select2-container--classic .select2-results > .select2-results__options {
279308
max-height: 200px;
@@ -290,5 +319,4 @@
290319
display: block;
291320
padding: 6px; }
292321
.select2-container--classic.select2-container--open .select2-dropdown {
293-
border: 1px solid #5897fb;
294-
border-top: none; }
322+
border-color: #5897fb; }

dist/css/select2.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/js/select2.amd.full.js

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,10 +2857,24 @@ define('select2/dropdown/attachBody',[
28572857
AttachBody.prototype.bind = function (decorated, container, $container) {
28582858
var self = this;
28592859

2860+
var setupResultsEvents = false;
2861+
28602862
decorated.call(this, container, $container);
28612863

28622864
container.on('open', function () {
28632865
self._showDropdown();
2866+
2867+
if (!setupResultsEvents) {
2868+
setupResultsEvents = true;
2869+
2870+
container.on('results:all', function () {
2871+
self._positionDropdown();
2872+
});
2873+
2874+
container.on('results:append', function () {
2875+
self._positionDropdown();
2876+
});
2877+
}
28642878
});
28652879

28662880
container.on('close', function () {
@@ -2904,9 +2918,64 @@ define('select2/dropdown/attachBody',[
29042918
};
29052919

29062920
AttachBody.prototype._positionDropdown = function () {
2907-
var css = this.$container.offset();
2921+
var $window = $(window);
2922+
2923+
var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
2924+
var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
2925+
2926+
var newDirection = null;
2927+
2928+
var position = this.$container.position();
2929+
var offset = this.$container.offset();
2930+
2931+
offset.bottom = offset.top + this.$container.outerHeight(false);
2932+
2933+
var container = {
2934+
height: this.$container.outerHeight(false)
2935+
};
2936+
2937+
container.top = offset.top;
2938+
container.bottom = offset.top + container.height;
29082939

2909-
css.top += this.$container.outerHeight(true);
2940+
var dropdown = {
2941+
height: this.$dropdown.outerHeight(false)
2942+
};
2943+
2944+
var viewport = {
2945+
top: $window.scrollTop(),
2946+
bottom: $window.scrollTop() + $window.height()
2947+
};
2948+
2949+
var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
2950+
var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
2951+
2952+
var css = {
2953+
left: offset.left,
2954+
top: container.bottom
2955+
};
2956+
2957+
if (!isCurrentlyAbove && !isCurrentlyBelow) {
2958+
newDirection = 'below';
2959+
}
2960+
2961+
if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
2962+
newDirection = 'above';
2963+
} else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
2964+
newDirection = 'below';
2965+
}
2966+
2967+
if (newDirection == 'above' || isCurrentlyAbove) {
2968+
css.top = container.top - dropdown.height;
2969+
}
2970+
2971+
if (newDirection != null) {
2972+
this.$dropdown
2973+
.removeClass('select2-dropdown--below select2-dropdown--above')
2974+
.addClass('select2-dropdown--' + newDirection);
2975+
this.$container
2976+
.removeClass('select2-container--below select2-container--above')
2977+
.addClass('select2-container--' + newDirection);
2978+
}
29102979

29112980
this.$dropdownContainer.css(css);
29122981
};

dist/js/select2.amd.js

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2857,10 +2857,24 @@ define('select2/dropdown/attachBody',[
28572857
AttachBody.prototype.bind = function (decorated, container, $container) {
28582858
var self = this;
28592859

2860+
var setupResultsEvents = false;
2861+
28602862
decorated.call(this, container, $container);
28612863

28622864
container.on('open', function () {
28632865
self._showDropdown();
2866+
2867+
if (!setupResultsEvents) {
2868+
setupResultsEvents = true;
2869+
2870+
container.on('results:all', function () {
2871+
self._positionDropdown();
2872+
});
2873+
2874+
container.on('results:append', function () {
2875+
self._positionDropdown();
2876+
});
2877+
}
28642878
});
28652879

28662880
container.on('close', function () {
@@ -2904,9 +2918,64 @@ define('select2/dropdown/attachBody',[
29042918
};
29052919

29062920
AttachBody.prototype._positionDropdown = function () {
2907-
var css = this.$container.offset();
2921+
var $window = $(window);
2922+
2923+
var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
2924+
var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
2925+
2926+
var newDirection = null;
2927+
2928+
var position = this.$container.position();
2929+
var offset = this.$container.offset();
2930+
2931+
offset.bottom = offset.top + this.$container.outerHeight(false);
2932+
2933+
var container = {
2934+
height: this.$container.outerHeight(false)
2935+
};
2936+
2937+
container.top = offset.top;
2938+
container.bottom = offset.top + container.height;
29082939

2909-
css.top += this.$container.outerHeight(true);
2940+
var dropdown = {
2941+
height: this.$dropdown.outerHeight(false)
2942+
};
2943+
2944+
var viewport = {
2945+
top: $window.scrollTop(),
2946+
bottom: $window.scrollTop() + $window.height()
2947+
};
2948+
2949+
var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
2950+
var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
2951+
2952+
var css = {
2953+
left: offset.left,
2954+
top: container.bottom
2955+
};
2956+
2957+
if (!isCurrentlyAbove && !isCurrentlyBelow) {
2958+
newDirection = 'below';
2959+
}
2960+
2961+
if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
2962+
newDirection = 'above';
2963+
} else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
2964+
newDirection = 'below';
2965+
}
2966+
2967+
if (newDirection == 'above' || isCurrentlyAbove) {
2968+
css.top = container.top - dropdown.height;
2969+
}
2970+
2971+
if (newDirection != null) {
2972+
this.$dropdown
2973+
.removeClass('select2-dropdown--below select2-dropdown--above')
2974+
.addClass('select2-dropdown--' + newDirection);
2975+
this.$container
2976+
.removeClass('select2-container--below select2-container--above')
2977+
.addClass('select2-container--' + newDirection);
2978+
}
29102979

29112980
this.$dropdownContainer.css(css);
29122981
};

dist/js/select2.full.js

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12392,10 +12392,24 @@ define('select2/dropdown/attachBody',[
1239212392
AttachBody.prototype.bind = function (decorated, container, $container) {
1239312393
var self = this;
1239412394

12395+
var setupResultsEvents = false;
12396+
1239512397
decorated.call(this, container, $container);
1239612398

1239712399
container.on('open', function () {
1239812400
self._showDropdown();
12401+
12402+
if (!setupResultsEvents) {
12403+
setupResultsEvents = true;
12404+
12405+
container.on('results:all', function () {
12406+
self._positionDropdown();
12407+
});
12408+
12409+
container.on('results:append', function () {
12410+
self._positionDropdown();
12411+
});
12412+
}
1239912413
});
1240012414

1240112415
container.on('close', function () {
@@ -12439,9 +12453,64 @@ define('select2/dropdown/attachBody',[
1243912453
};
1244012454

1244112455
AttachBody.prototype._positionDropdown = function () {
12442-
var css = this.$container.offset();
12456+
var $window = $(window);
12457+
12458+
var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
12459+
var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');
12460+
12461+
var newDirection = null;
12462+
12463+
var position = this.$container.position();
12464+
var offset = this.$container.offset();
12465+
12466+
offset.bottom = offset.top + this.$container.outerHeight(false);
12467+
12468+
var container = {
12469+
height: this.$container.outerHeight(false)
12470+
};
12471+
12472+
container.top = offset.top;
12473+
container.bottom = offset.top + container.height;
1244312474

12444-
css.top += this.$container.outerHeight(true);
12475+
var dropdown = {
12476+
height: this.$dropdown.outerHeight(false)
12477+
};
12478+
12479+
var viewport = {
12480+
top: $window.scrollTop(),
12481+
bottom: $window.scrollTop() + $window.height()
12482+
};
12483+
12484+
var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
12485+
var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);
12486+
12487+
var css = {
12488+
left: offset.left,
12489+
top: container.bottom
12490+
};
12491+
12492+
if (!isCurrentlyAbove && !isCurrentlyBelow) {
12493+
newDirection = 'below';
12494+
}
12495+
12496+
if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
12497+
newDirection = 'above';
12498+
} else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
12499+
newDirection = 'below';
12500+
}
12501+
12502+
if (newDirection == 'above' || isCurrentlyAbove) {
12503+
css.top = container.top - dropdown.height;
12504+
}
12505+
12506+
if (newDirection != null) {
12507+
this.$dropdown
12508+
.removeClass('select2-dropdown--below select2-dropdown--above')
12509+
.addClass('select2-dropdown--' + newDirection);
12510+
this.$container
12511+
.removeClass('select2-container--below select2-container--above')
12512+
.addClass('select2-container--' + newDirection);
12513+
}
1244512514

1244612515
this.$dropdownContainer.css(css);
1244712516
};

dist/js/select2.full.min.js

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)