Skip to content

Loading…

Options list detaches from input when page scrolled #149

Closed
Lightw3ight opened this Issue · 28 comments

7 participants

@Lightw3ight

I have a Select2 multi select box within a scrollable div (display:auto)

when i click on my Select2 multi select box, the options list comes up as expected, listing all available options
now if i use my mouse scroll wheel while my mouse is positioned away from the options list, my underlying content scrolls but my options list stays in place, becoming detached from the multi select list.

a possible fix could be to create an invisible div that takes up the whole page, positioned directly under the options list that could swallow the mouse wheel events?

I tried this by adding a line into the open function of Select2.js
$("

").addClass("shield").appendTo(document.body);

and adding the following css class

.shield
{
position:absolute;
top:0px;
right:0px;
bottom:0px;
left:0px;
opacity:0;
filter: alpha(opacity=0);
background-color:White;
z-index:998;
}

obviously the div needs to be removed again but the concept worked (tested in chrome and ie)

@ivaynberg

i dont like the idea of an invisible div covering the whole page because it will intercept clicks. a better way is to listen to scroll and realign the dropdown. can you create a jsfiddle that reproduces this scenario so i have something to play with? thanks

@ProLoser

This happens for me too, but it is due to the select element being inside a container that is position:fixed. I was playing with different settings, such as making the dropmenu position:fixed but the problem with this is that the element's calculated offset is the TRUE offset + the amount of scrolling the page has done, making the dropmenu appear correctly when you are scrolled to the top and doesn't move when scrolling. With pos:abs on the dropmenu, it will always appear in the correct location, but scrolling will affect it.

Calculating if a container of the target select is pos:fixed is highly unreasonable however, I don't have a good solution

@nzgeek

One way to get around this would to be to allow a parent element to be passed in as an option to the .select2() function. If that option isn't there, the code does what it does now and inserts the drop container div into the body. If the option is there, the drop container div is inserted into to that element instead.

This gives some flexibility, and doesn't break things for existing sites.

@ProLoser

I'm not sure I get how this fixes anything. Personally the errors I describe are a matter of pos:fixed vs pos:abs

If this really was to be fixed, i think it would be best to pass an 'fixed' option, in which case position:fixed would be used on the dropmenu, instead of absolute, and the offset would be calculated differently. It's nearly impossible to simply "detect" if the original select is fixed or not (since this could be applied to a container).

@nzgeek

Sorry, I should have made it clearer which issue I was talking about. I was addressing the issue that Lightw3ight posted about, where a position:absolute drop-down div was acting as if it had position:fixed. Your issue with the position:fixed container is different (but in a way related).

The problem I've seen is where the select control is inside a part of the page that scrolls independently of the body. In my case, I have a div with overflow:scroll set, and the form inside this div is large enough that it causes scroll bars to be shown. When select2 creates a .select2-drop div to contain the list items, it appends the control to the document body. If you scroll the content inside the div (changing the location of the select) the body doesn't scroll with it, and therefore the .select2-drop div doesn't know to move either.

That's why I suggested that there be an option that allows something other than body to be used as the parent for the .select2-drop divs. If these divs can be parented somewhere inside the scrollable area, they'll move when the rest of the content scrolls.

I've got another idea that I might try out, which might solve everyone's problems (if it works). The CSS spec says that any position:absolute items are offset from their nearest position:relative ancestor. The code could look for the nearest position:relative ancestor of the select and could use that element for both offset calculations and as the parent for the .select2-drop div. This helps in both cases because it allows you to force where in the DOM the .select-drop divs are inserted. If you need them inside an overflow:scroll area, you can get them put there. If you need them inside a position:fixed area, that's possible too.

@ivaynberg

the dropdown dom has to be the last child of the body tag in order to appear "over" everything else. if it is inside another element then it may be overlayed by other elements.

for example when it is opened inside a modal the dropdown should not be wrapped by the modal's main div, it should be rendered over it. the only way to achieve that, afaik is to make it the last child of body.

@ProLoser
@ivaynberg

i dont know if its unfixable, we can probably listen to window.scroll and reposition any open dropdown....

@Lightw3ight

The answer is possibly a lot easier than we think

If you look at a standard select list, when the drop down is opened, if you scroll, it closes the drop down... should be pretty easy to hook into the mousewheel event and close the select2 dropdown

@nzgeek

I agree completely. It's far easier to just hide the drop-down part of the list than it is to try and figure out if it should be moved or not.

If you do this, please make sure that you listen for more than just the window.scroll() event. If the select is inside a div with overflow:scroll set, window.scroll() doesn't get called if only the div scrolls. You will need to walk the .parent() chain to look for any items with .css("overflow") == "scroll", and add the .scroll() handler to those too.

@ProLoser

This would actually be disastrous. When I scroll on the resultlist and hit the end of the list, the rest of the page scrolls. You'd essentially be closing the dropmenu as soon as you reach the end of the result list on accident constantly.

@nzgeek

I've given up trying to get a one-fits-all solution for this. I've modified my general page script so that it hooks into the scroll() event and calls .select("close") on every item in there. It's kinda dirty, but it works.

I still think it's worth looking at whether the parent for the drop-down div could be optionally added to something other than the body. In ProLoser's case, it could be added to the foot of the position:fixed div, and would therefore not scroll with the rest of the content. In my case, it could be added to the end of the div that forms the tab panel body, and would therefore scroll when the document doesn't. It may not be perfect, and most people will never use it, but it might help to work around the occasional problem.

@ivaynberg

@ProLoser if you install the mousewheel plugin the list wont scroll past the end. see the source of the demo page.

@pedroteixeira

This is an important issue, because it prevents select2 from being used inside a modal (using bootstrap here)

I guess the best strategy would be to re-position the dropdown. Started to play with the following solution:

$(document).on('scroll', function(e) {
$(".select2-drop.select2-drop-active.select2-with-searchbox").css('top', 86 + $(window).scrollTop() + 'px');
});

where 86 was the original top value..

It has some flickering, perhaps we should add some animation?

@pedroteixeira

we should probably just bind to the scroll event when necessary. Current workaround: https://gist.github.com/3250878

@ProLoser
@ivaynberg

can someone make a fiddle so i can play around with this?

@pedroteixeira $(".select-dropdown-open").select2("positionDropdown")

@ivaynberg

i think the above commit should fix it. if not, please create a jsfiddle.

@ivaynberg ivaynberg closed this
@ryfeng

@ivaynberg The above commit doesn't fix the original issue.

JSFiddle (with proposed solution):
http://jsfiddle.net/v8ApN/23/

@ivaynberg ivaynberg reopened this
@ryfeng

Actually,

$(':scrollable').scroll(function() {
    $(".select2-container.select2-dropdown-open").not($(this)).select2('positionDropdown');
});

Might do the trick?
http://jsfiddle.net/v8ApN/24/

@ivaynberg

yes, you can either reposition the dropdown or close it. native behavior seems to be to close it.

i am not sure what the best solution for this kind of thing is. maybe we can look through parents to see if there are some with scroll overflow and attach a scroll listener to those...

@ryfeng

One extreme edge case is where scrolling a non-parent would affect the positioning of the container. Something like scrolling through an option list would trigger an editing blurb to appear.

Of course, that opens the entire can of worms to anything that can modify the position of the container - an ajax callback, polling function, etc. The only real solution is using DOMAttrModified afaik.

This seems to also do the trick:
http://www.west-wind.com/weblog/posts/2008/Sep/12/jQuery-CSS-Property-Monitoring-Plugin-updated

@ivaynberg

yeah, i suppose a timeout that runs every second and checks if select2 has moved can do the trick...is that something we want?

@ryfeng

I think the simple case of scrolling parent element is a must-fix - this would be the bare minimum.

The general case is not as high a priority, but it should be simple enough to implement that it wouldn't require much effort.

  • Create an instance variable in init() for the timer
  • Create an instance offset variable/tuple
  • Create a new polling function utilizing the timer variable - calls setTimeout
    • Checks this.container.offset() against the instance offset variable
  • Initially call the polling function in opening()
  • Clear the timeout clearTimeout in close()
@Lightw3ight

I say just stick with native behavior, seems like it would be simpler to implement

@ryfeng

Well this is surprising:
http://jsfiddle.net/89nvc/

Chrome detaches the dropdown.
Firefox seems to do a poll (there's some bumpiness) - but i'm not entirely sure.
IE seems to be the only one that is smooth.

Now i'm wondering what default behavior is.

@nzgeek

I'm against the use of polling in order to do this, primarily because there will almost always be a perceptible delay between the scroll event and when the drop-down gets repositioned. This looks quite messy and unprofessional.

It's also bad from an environmental point of view (yes, I'm being serious!). By polling for activity every second, the browser process is forced to remain active and in memory. This will keep the processor in a higher-power state, which uses more power. This isn't so terrible on a desktop machine, but it would certainly contribute to battery usage on a mobile device.

It would be better to walk the parent nodes to find any that can be scrollable (overflow:scroll or overflow:auto) and attach scroll handlers to those. You can then either close the drop-down (browser default behaviour) or reposition it if you have to.

@ivaynberg ivaynberg closed this in d92a572
@evantbyrne

I ran into this issue as well with the bootstrap modals. I don't know if it was stated before in this thread, but my solution was to simply set the modal to position:absolute and force the browser to scroll up to the top of the screen. I figured this was the best solution, because position:fixed isn't supported in mobile browsers anyways.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.