Skip to content

Commit 003d605

Browse files
committed
Prevent scrolling in scrollable containers
This prevents scrolling within scrollable containers (excluding the dropdown) when the dropdown is open. This fixes an issue where the dropdown would go out of sync with the parent container when the parent container was scrolled. We did not have this issue in past versions of Select2 because the mask prevented any scrolling. Now that we have removed the mask, we have to deal with scrolling from different areas of the page. We initially tried to hook into the `scroll` events of the parent containers, but because of a list of issues we decided against it. If the container scrolled out of view, the dropdown would still be left open and above everything else, even though the container wasn't visually connected to it. The `scroll` event does not bubble, so we need to attach the `scroll` handler to every parent element that is scrollable. Since it is surprisingly difficult to determine if an element is scrollable, we modified some CC-BY-SA code and use that to determine if the element has a scrollbar. The original `hasScroll` function can be found at http://codereview.stackexchange.com/q/13338, the same link left within the code, and was originally designed to be a sizzle selector. As Select2 does not require a sizzle-compatible version of jQuery, we converted it into a function that could be used with `.filter` to filter down the elements. This closes select2#2975.
1 parent 0006534 commit 003d605

8 files changed

Lines changed: 224 additions & 14 deletions

File tree

dist/js/select2.amd.full.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,31 @@ define(['jquery'], function ($) {define('select2/utils',[], function () {
193193
return data;
194194
};
195195

196+
Utils.hasScroll = function (index, el) {
197+
// Adapted from the function created by @ShadowScripter
198+
// and adapted by @BillBarry on the Stack Exchange Code Review website.
199+
// The original code can be found at
200+
// http://codereview.stackexchange.com/q/13338
201+
// and was designed to be used with the Sizzle selector engine.
202+
203+
var $el = $(el);
204+
var overflowX = el.style.overflowX;
205+
var overflowY = el.style.overflowY;
206+
207+
//Check both x and y declarations
208+
if (overflowX === overflowY &&
209+
(overflowY === 'hidden' || overflowY === 'visible')) {
210+
return false;
211+
}
212+
213+
if (overflowX === 'scroll' || overflowY === 'scroll') {
214+
return true;
215+
}
216+
217+
return ($el.innerHeight() < el.scrollHeight ||
218+
$el.innerWidth() < el.scrollWidth);
219+
};
220+
196221
return Utils;
197222
});
198223

@@ -3252,8 +3277,9 @@ define('select2/dropdown/infiniteScroll',[
32523277
});
32533278

32543279
define('select2/dropdown/attachBody',[
3255-
'jquery'
3256-
], function ($) {
3280+
'jquery',
3281+
'../utils'
3282+
], function ($, Utils) {
32573283
function AttachBody (decorated, $element, options) {
32583284
this.$dropdownParent = options.get('dropdownParent') || document.body;
32593285

@@ -3333,6 +3359,19 @@ define('select2/dropdown/attachBody',[
33333359
var resizeEvent = 'resize.select2.' + container.id;
33343360
var orientationEvent = 'orientationchange.select2.' + container.id;
33353361

3362+
$watchers = this.$container.parents().filter(Utils.hasScroll);
3363+
$watchers.each(function () {
3364+
$(this).data('select2-scroll-position', {
3365+
x: $(this).scrollLeft(),
3366+
y: $(this).scrollTop()
3367+
});
3368+
});
3369+
3370+
$watchers.on(scrollEvent, function (ev) {
3371+
var position = $(this).data('select2-scroll-position');
3372+
$(this).scrollTop(position.y);
3373+
});
3374+
33363375
$(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
33373376
function (e) {
33383377
self._positionDropdown();
@@ -3345,6 +3384,9 @@ define('select2/dropdown/attachBody',[
33453384
var resizeEvent = 'resize.select2.' + container.id;
33463385
var orientationEvent = 'orientationchange.select2.' + container.id;
33473386

3387+
$watchers = this.$container.parents().filter(Utils.hasScroll);
3388+
$watchers.off(scrollEvent);
3389+
33483390
$(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
33493391
};
33503392

dist/js/select2.amd.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,31 @@ define(['jquery'], function ($) {define('select2/utils',[], function () {
193193
return data;
194194
};
195195

196+
Utils.hasScroll = function (index, el) {
197+
// Adapted from the function created by @ShadowScripter
198+
// and adapted by @BillBarry on the Stack Exchange Code Review website.
199+
// The original code can be found at
200+
// http://codereview.stackexchange.com/q/13338
201+
// and was designed to be used with the Sizzle selector engine.
202+
203+
var $el = $(el);
204+
var overflowX = el.style.overflowX;
205+
var overflowY = el.style.overflowY;
206+
207+
//Check both x and y declarations
208+
if (overflowX === overflowY &&
209+
(overflowY === 'hidden' || overflowY === 'visible')) {
210+
return false;
211+
}
212+
213+
if (overflowX === 'scroll' || overflowY === 'scroll') {
214+
return true;
215+
}
216+
217+
return ($el.innerHeight() < el.scrollHeight ||
218+
$el.innerWidth() < el.scrollWidth);
219+
};
220+
196221
return Utils;
197222
});
198223

@@ -3252,8 +3277,9 @@ define('select2/dropdown/infiniteScroll',[
32523277
});
32533278

32543279
define('select2/dropdown/attachBody',[
3255-
'jquery'
3256-
], function ($) {
3280+
'jquery',
3281+
'../utils'
3282+
], function ($, Utils) {
32573283
function AttachBody (decorated, $element, options) {
32583284
this.$dropdownParent = options.get('dropdownParent') || document.body;
32593285

@@ -3333,6 +3359,19 @@ define('select2/dropdown/attachBody',[
33333359
var resizeEvent = 'resize.select2.' + container.id;
33343360
var orientationEvent = 'orientationchange.select2.' + container.id;
33353361

3362+
$watchers = this.$container.parents().filter(Utils.hasScroll);
3363+
$watchers.each(function () {
3364+
$(this).data('select2-scroll-position', {
3365+
x: $(this).scrollLeft(),
3366+
y: $(this).scrollTop()
3367+
});
3368+
});
3369+
3370+
$watchers.on(scrollEvent, function (ev) {
3371+
var position = $(this).data('select2-scroll-position');
3372+
$(this).scrollTop(position.y);
3373+
});
3374+
33363375
$(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
33373376
function (e) {
33383377
self._positionDropdown();
@@ -3345,6 +3384,9 @@ define('select2/dropdown/attachBody',[
33453384
var resizeEvent = 'resize.select2.' + container.id;
33463385
var orientationEvent = 'orientationchange.select2.' + container.id;
33473386

3387+
$watchers = this.$container.parents().filter(Utils.hasScroll);
3388+
$watchers.off(scrollEvent);
3389+
33483390
$(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
33493391
};
33503392

dist/js/select2.full.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,31 @@ define('select2/utils',[], function () {
631631
return data;
632632
};
633633

634+
Utils.hasScroll = function (index, el) {
635+
// Adapted from the function created by @ShadowScripter
636+
// and adapted by @BillBarry on the Stack Exchange Code Review website.
637+
// The original code can be found at
638+
// http://codereview.stackexchange.com/q/13338
639+
// and was designed to be used with the Sizzle selector engine.
640+
641+
var $el = $(el);
642+
var overflowX = el.style.overflowX;
643+
var overflowY = el.style.overflowY;
644+
645+
//Check both x and y declarations
646+
if (overflowX === overflowY &&
647+
(overflowY === 'hidden' || overflowY === 'visible')) {
648+
return false;
649+
}
650+
651+
if (overflowX === 'scroll' || overflowY === 'scroll') {
652+
return true;
653+
}
654+
655+
return ($el.innerHeight() < el.scrollHeight ||
656+
$el.innerWidth() < el.scrollWidth);
657+
};
658+
634659
return Utils;
635660
});
636661

@@ -3690,8 +3715,9 @@ define('select2/dropdown/infiniteScroll',[
36903715
});
36913716

36923717
define('select2/dropdown/attachBody',[
3693-
'jquery'
3694-
], function ($) {
3718+
'jquery',
3719+
'../utils'
3720+
], function ($, Utils) {
36953721
function AttachBody (decorated, $element, options) {
36963722
this.$dropdownParent = options.get('dropdownParent') || document.body;
36973723

@@ -3771,6 +3797,19 @@ define('select2/dropdown/attachBody',[
37713797
var resizeEvent = 'resize.select2.' + container.id;
37723798
var orientationEvent = 'orientationchange.select2.' + container.id;
37733799

3800+
$watchers = this.$container.parents().filter(Utils.hasScroll);
3801+
$watchers.each(function () {
3802+
$(this).data('select2-scroll-position', {
3803+
x: $(this).scrollLeft(),
3804+
y: $(this).scrollTop()
3805+
});
3806+
});
3807+
3808+
$watchers.on(scrollEvent, function (ev) {
3809+
var position = $(this).data('select2-scroll-position');
3810+
$(this).scrollTop(position.y);
3811+
});
3812+
37743813
$(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
37753814
function (e) {
37763815
self._positionDropdown();
@@ -3783,6 +3822,9 @@ define('select2/dropdown/attachBody',[
37833822
var resizeEvent = 'resize.select2.' + container.id;
37843823
var orientationEvent = 'orientationchange.select2.' + container.id;
37853824

3825+
$watchers = this.$container.parents().filter(Utils.hasScroll);
3826+
$watchers.off(scrollEvent);
3827+
37863828
$(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
37873829
};
37883830

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.

dist/js/select2.js

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,6 +631,31 @@ define('select2/utils',[], function () {
631631
return data;
632632
};
633633

634+
Utils.hasScroll = function (index, el) {
635+
// Adapted from the function created by @ShadowScripter
636+
// and adapted by @BillBarry on the Stack Exchange Code Review website.
637+
// The original code can be found at
638+
// http://codereview.stackexchange.com/q/13338
639+
// and was designed to be used with the Sizzle selector engine.
640+
641+
var $el = $(el);
642+
var overflowX = el.style.overflowX;
643+
var overflowY = el.style.overflowY;
644+
645+
//Check both x and y declarations
646+
if (overflowX === overflowY &&
647+
(overflowY === 'hidden' || overflowY === 'visible')) {
648+
return false;
649+
}
650+
651+
if (overflowX === 'scroll' || overflowY === 'scroll') {
652+
return true;
653+
}
654+
655+
return ($el.innerHeight() < el.scrollHeight ||
656+
$el.innerWidth() < el.scrollWidth);
657+
};
658+
634659
return Utils;
635660
});
636661

@@ -3690,8 +3715,9 @@ define('select2/dropdown/infiniteScroll',[
36903715
});
36913716

36923717
define('select2/dropdown/attachBody',[
3693-
'jquery'
3694-
], function ($) {
3718+
'jquery',
3719+
'../utils'
3720+
], function ($, Utils) {
36953721
function AttachBody (decorated, $element, options) {
36963722
this.$dropdownParent = options.get('dropdownParent') || document.body;
36973723

@@ -3771,6 +3797,19 @@ define('select2/dropdown/attachBody',[
37713797
var resizeEvent = 'resize.select2.' + container.id;
37723798
var orientationEvent = 'orientationchange.select2.' + container.id;
37733799

3800+
$watchers = this.$container.parents().filter(Utils.hasScroll);
3801+
$watchers.each(function () {
3802+
$(this).data('select2-scroll-position', {
3803+
x: $(this).scrollLeft(),
3804+
y: $(this).scrollTop()
3805+
});
3806+
});
3807+
3808+
$watchers.on(scrollEvent, function (ev) {
3809+
var position = $(this).data('select2-scroll-position');
3810+
$(this).scrollTop(position.y);
3811+
});
3812+
37743813
$(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
37753814
function (e) {
37763815
self._positionDropdown();
@@ -3783,6 +3822,9 @@ define('select2/dropdown/attachBody',[
37833822
var resizeEvent = 'resize.select2.' + container.id;
37843823
var orientationEvent = 'orientationchange.select2.' + container.id;
37853824

3825+
$watchers = this.$container.parents().filter(Utils.hasScroll);
3826+
$watchers.off(scrollEvent);
3827+
37863828
$(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
37873829
};
37883830

dist/js/select2.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.

src/js/select2/dropdown/attachBody.js

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
define([
2-
'jquery'
3-
], function ($) {
2+
'jquery',
3+
'../utils'
4+
], function ($, Utils) {
45
function AttachBody (decorated, $element, options) {
56
this.$dropdownParent = options.get('dropdownParent') || document.body;
67

@@ -80,6 +81,19 @@ define([
8081
var resizeEvent = 'resize.select2.' + container.id;
8182
var orientationEvent = 'orientationchange.select2.' + container.id;
8283

84+
$watchers = this.$container.parents().filter(Utils.hasScroll);
85+
$watchers.each(function () {
86+
$(this).data('select2-scroll-position', {
87+
x: $(this).scrollLeft(),
88+
y: $(this).scrollTop()
89+
});
90+
});
91+
92+
$watchers.on(scrollEvent, function (ev) {
93+
var position = $(this).data('select2-scroll-position');
94+
$(this).scrollTop(position.y);
95+
});
96+
8397
$(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
8498
function (e) {
8599
self._positionDropdown();
@@ -92,6 +106,9 @@ define([
92106
var resizeEvent = 'resize.select2.' + container.id;
93107
var orientationEvent = 'orientationchange.select2.' + container.id;
94108

109+
$watchers = this.$container.parents().filter(Utils.hasScroll);
110+
$watchers.off(scrollEvent);
111+
95112
$(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
96113
};
97114

src/js/select2/utils.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,5 +193,30 @@ define([], function () {
193193
return data;
194194
};
195195

196+
Utils.hasScroll = function (index, el) {
197+
// Adapted from the function created by @ShadowScripter
198+
// and adapted by @BillBarry on the Stack Exchange Code Review website.
199+
// The original code can be found at
200+
// http://codereview.stackexchange.com/q/13338
201+
// and was designed to be used with the Sizzle selector engine.
202+
203+
var $el = $(el);
204+
var overflowX = el.style.overflowX;
205+
var overflowY = el.style.overflowY;
206+
207+
//Check both x and y declarations
208+
if (overflowX === overflowY &&
209+
(overflowY === 'hidden' || overflowY === 'visible')) {
210+
return false;
211+
}
212+
213+
if (overflowX === 'scroll' || overflowY === 'scroll') {
214+
return true;
215+
}
216+
217+
return ($el.innerHeight() < el.scrollHeight ||
218+
$el.innerWidth() < el.scrollWidth);
219+
};
220+
196221
return Utils;
197222
});

0 commit comments

Comments
 (0)