.NET (295) administrative (41) Ajax (42) AngularJS (2) ASP.NET (144) bicycle (2) books (180) browser (8) C# (133) cars (1) chess (28) CodePlex (10) Coma (8) database (47) deployment (3) Entity Framework (2) essay (110) flash/shockwave (2) flex (1) food (3) friend (2) game (20) idea (5) IIS (8) javascript (82) LInQ (2) Linux (6) management (4) manga (42) misc (670) mobile (1) movies (90) MsAccess (1) murder (2) music (64) mysql (1) news (99) permanent (1) personal (68) PHP (1) physics (2) picture (307) places (12) politics (13) programming (503) rant (120) religion (3) science (43) Sharepoint (3) software (58) space (1) T4 (2) technology (11) Test Driven Development (4) translation (2) VB (2) video (97) Visual Studio (44) web design (46) Windows API (8) Windows Forms (3) Windows Server (5) WPF/Silverlight (63) XML (11)

Wednesday, June 27, 2007

Setting vertical scroll property in selects with Javascript

I've updated this page a few times, adding more optimizations, so get the last version.

I was asked to find a way to maintain the vertical scroll in a selection box (that is a select html element with a size bigger than 1). I checked to see what property was changing when I was scrolling the select and I noticed that scrollTop was the one. So I used
sel.scrollTop=value;
.

Well, this works fine in FireFox and apparently works fine in IE7, too. However, in Internet Explorer 7, if you click on the up/down arrows of the select scrollbar, the scroll resets to 0. Worst than that, in IE6 you can't even set the scrollTop property. You don't get an error, but it doesn't work.

One suggested solution for people that tried the same thing is to set the size attribute to the number of options, thus getting rid of the select scrollbar, and place the select in a div with fixed height and overflow auto. It will look like a select listbox, but the scroll will be from the div and easily changed. I didn't choose this solution, basically because I felt it was cheating.

So, I've applied another solution, one that changes the selected item so that the select element scrolls itself to a position as close to the desired vertical scroll position as possible. Then, I set the scrollTop property, so that it goes at that exact position in IE7 and FireFox. If one clicks the up/down scroll arrows in IE7, the scroll position resets to the one found by the selectedIndex, not 0. And it works in IE6, too.

Here is the code:
JS Code - vertical scroll a select element
function setSelectVerticalScroll(sel,y) {
if (!sel||(sel.options.length==0)) return;

// remember the selectedIndex (for single selection selects)
var selectedIndex=sel.selectedIndex;

// find the item that selected will yield
// the best match for the required scrollTop
var best=-1; var bestMatch=100000;

// try to guess the starting index based on select height
var optionHeight=parseInt(sel.size)
?parseInt(sel.offsetHeight)/(parseInt(sel.size)+0.0)
:parseInt(sel.offsetHeight);
var startIndex=parseInt(y/optionHeight);
if (startIndex>=sel.options.length) startIndex=sel.options.length-1;
var c=startIndex;
while (c<sel.options.length) {
var selected=sel.options[c].selected;
sel.options[c].selected=false;
sel.options[c].selected=true;
if (Math.abs(parseInt(sel.scrollTop)-y)<bestMatch) {
bestMatch=Math.abs(parseInt(sel.scrollTop)-y);
best=c;
}
sel.options[c].selected=selected;
// best match has been found, no point of going further
if (Math.abs(parseInt(sel.scrollTop)-y)>bestMatch) break;

// try to jump to the right index
var inc=parseInt((y-parseInt(sel.scrollTop))/optionHeight);
c+=(inc>0?inc:1);
}

// select best match, to force scrolling
if (best>=0) {
var selected=sel.options[best].selected;
sel.options[best].selected=false;
sel.options[best].selected=true;
sel.options[best].selected=selected;
}

// set the selection back
if (sel.selectedIndex!=selectedIndex)
sel.selectedIndex=selectedIndex;

// now this should have been enough,
// but it doesn't work in IE6 and it's bugged in IE7
sel.scrollTop=y;
}


Warning! This will not work if the select is hidden by way of display=none or visibility=hidden. Also, for large selects, it will look funny scrolling through all the options. Optimizations can be applied, that try to find the correct selectedIndex or stop after the scroll position has been found (like in this example) or that search the best scrollTop match by dividing the options in two parts rather than taking them one by one, etc.

12 comments:

Anonymous said...

Hi,

I understand that setting the selected option will force the select to scroll to the desired position, but for the same reason, how come setting the selected index back to its original value does not scroll the select back so that the original selected option is in view again?

Siderite said...

If I understand your question correctly, you ask how come the select will not scroll to the before last selected item when you unselect the last selected? Then the answer is that the scroll to the selected item happends only at selection, not unselection.

The code above will go through all items unselecting and selecting them (thus forcing scroll), then setting their original value, checking where is the vertical scroll and remembering the closest match to the desired vertical location.

At the end, it just unselects,selects and sets the original value to the best match.

Anonymous said...

>>If I understand your question correctly, you ask how come the select will not scroll to the before last selected item when you unselect the last selected?

More precisely "when you set the selection back "

I tried your code. It works when the original selection is also visible in the new scrolled viewport after the function is applied.
But, if the original selection lies outside, when you restore it, the window is scrolled again so that the original selected option is in view again.

Try this code below:
After you click on the buton, the select scrols, but will always comes back at its original state.

<SELECT ID="MySelect" SIZE="10">
<option>Option 0
<option>Option 1
<option>Option 2
<option>Option 3
<option>Option 4
<option>Option 5
<option>Option 6
<option>Option 7
<option>Option 8
<option>Option 9
<option>Option 10
<option>Option 11
<option>Option 12
<option>Option 13
<option>Option 14
<option>Option 15
<option>Option 16
<option>Option 17
<option>Option 18
<option selected>Option 19
</select>
<INPUT TYPE="button" VALUE="scroll" onclick="scrollSel()">
<script>

function scrollSel()
{
setSelectVerticalScroll(document.getElementById('MySelect'),100)
}
function setSelectVerticalScroll(sel,y) {
...

Siderite said...

Yes, you are right! Now I understand what you mean. If you set the select to multiple selection you will see that it works. I will have to tweak this a little bit to make it work, but I don't have the time. Thanks for your comment!

Anonymous said...

>>If you set the select to multiple selection you will see that it works.

For multiple select, yes, it works, because when you select the "best" option, you don't loose the original selection, and you don't have to care about the selectedIndex.
But for non multiple select, you can't select an option to force scrolling and at the end restore the original select the same way but with no scrolling effetct.

>>I will have to tweak this a little bit to make it work.

How about changing the select to multiple, and restore it at the end?
Let me try this...

Anonymous said...

>>Let me try this...

... Too bad. It will also scroll the select to its original position :-(

Siderite said...

I don't think it works, period. Not with changing selected items. Maybe some other javascript magic?

Anonymous said...

How about this:
- make the select multiple;
- scroll it;
- add an onclick event to make sure that the select will act as a non multiple select;
- finally restore the non multiple state: it will scroll to the new position depending on the option clicked, but this time it is ok.

But is it really worth? Explore 6 is soon becoming obsolete any way.

Indoom said...

Hello.

I know this is kinda old post, but I search the internet for scrolling the select list and couldn't find anything useful. But your idea actually gave me some ideas. So I managed to make select to scroll cross browser. Tested on IE6,7, FF2, Safari win, Opera 2.25
I made select box multiple as default, and simulated single selection. I used that selecting and deselecting an option for scrolling and, for some browsers, .scrollTop too. There also even can be selected option. It wont lose its selection on scrolling.
http://indoom.cgimage.lv/jstests/testselect.htm
There is working test, with search input to test the scrolling.

Siderite said...

Well, I am glad I could help. Keeping the select multiple and simulating the single behavior is a pretty nice idea. I am a bit envious I didn't think of it myself.

Anonymous said...

http://weblogs.asp.net/andrewfrederick/archive/2008/03/04/maintain-scroll-position-after-asynchronous-postback.aspx

Siderite said...

Not that I don't like spam URLs or anything, but your blog entry doesn't have anything to do with select elements.