21

I've come across various solutions to this issue on the net.

Basically, I find having to hold down ctrl a bit cheesy, and I want the select list to just select whatever I click on and add it to the currently selected items.

I've already got this code:

    $("select[multiple] option").mousedown(function () {
        var $self = $(this);            

        if ($self.attr("selected")) {                
            $self.removeAttr("selected", "");
        }
        else {
            $self.attr("selected", "selected");
        }

        return false;            
    });

Element:

    <select multiple id="WOStatusKey" name="WOStatusKey">                
        <option value="1">Created</option>
        <option value="2">In Process</option>
        <!-- etc. (about 20 of these) -->
    </select>

It works fine with one exception: any time something is selected/deselected that is not at the top of the list (where you have to scroll to see it) then it pops back up to the top after you select it. I've played around with it a little bit but can't figure out anything to prevent this behavior. Also, I've seen a couple other solutions to this problem, although nothing that either works or works well.

I only need this to work in Chrome. Also, I am not interested in any alternative solutions to using a select list.

Thank you for any help, it is much appreciated.

Zatos
  • 299
  • 1
  • 3
  • 15
  • Can you make a fiddle for this so others could also experiment with your code? – aa333 Jul 03 '14 at 02:10
  • What browser? Not seeing the problem in chrome – mowwwalker Jul 03 '14 at 02:12
  • janaspage: on your link, scroll down and click on an option. After selection, the select box will automatically reset the position to be at the very top (as if you scrolled all the way back up). aa333: http://jsfiddle.net/h5Sw2/ – Zatos Jul 03 '14 at 18:51

7 Answers7

44

You can save the Element.scrollTop and set it at the end.

$("select").mousedown(function(e){
    e.preventDefault();

    var select = this;
    var scroll = select .scrollTop;

    e.target.selected = !e.target.selected;

    setTimeout(function(){select.scrollTop = scroll;}, 0);

    $(select ).focus();
}).mousemove(function(e){e.preventDefault()});

http://jsfiddle.net/UziTech/cjjg68dr/114/

Tony Brix
  • 4,085
  • 7
  • 41
  • 53
  • Thank you sir! I never did find a solution. This works great! – Zatos Nov 25 '14 at 20:29
  • Finally a solution that works! btw, tested it on Safari/Opera/Chrome/FF, doesn't work for FF – evilReiko May 27 '15 at 05:37
  • This fiddle doesn't work in the latest Chrome; as the OP described, if you select, say #2, and then scroll down to #9, the select box will auto scroll to #2 again immediately after click. – Ruben Martinez Jr. Jan 27 '16 at 00:36
  • 2
    I updated the fiddle to set the scrollTop inside a setTimeout so it will update it at the end of the event loop – Tony Brix Jan 27 '16 at 02:23
  • IE considers the select element the target instead of the option element. As far as I know there is no way to do this in IE – Tony Brix Jan 27 '16 at 19:40
  • It works fine for me in IE 11. Also, it is the only solution that fits for me in all browsers. + 1 – eMi Nov 08 '16 at 21:59
  • 3
    If you have `onChange` handlers that `preventDefault()` is preventing from working, just add `$("#mySelect").trigger("change")`. You'll still get the Ctrl+click effect. – Noumenon Jan 26 '17 at 03:52
  • @Noumenon I like this idea, but where do you insert this `trigger("change")` part in the above code? – user1837608 Nov 30 '17 at 03:53
  • @user1837608 Right after `$(select).focus()`. – Noumenon Nov 30 '17 at 04:34
  • In chrome, if I click up/down buttons on the scrollbar, it is broken. As I unclick the up/down button, it scrolls 1 line then resets the scroll position to undo it. – Curtis Yallop Mar 21 '18 at 16:23
  • To fix up/down scroll buttons, see Cassio Ludewig's answer. – Curtis Yallop Mar 21 '18 at 16:44
  • @Tony Brix: I know this entry is kind of old but.. It is possible to get the correct option by using the click position, the scrollheight of the select and the amount of options. – Lain May 16 '19 at 14:52
  • @Lain I could see an issue if people have zoomed the page in or out. You would need an accurate measurement of the height of each option. – Tony Brix May 16 '19 at 15:40
  • @Tony Brix: IE does not provide the height of options sadly. That is why you need scrollHeight of the select and the amount of options (scrollHeight / options.length = optionHeight). I tested it quickly in my project and it works fine on zoomed in/out pages. I do not know if it works in all possible cases, but it works for me in my project _so far_ :-) – Lain May 17 '19 at 07:30
8

Tony's answer makes the select arrows buggy, as they work only if you hold the mouse down.

I've combined a few solutions into this, and it works fine at least in Chrome and FF:

// multiple select: selectable without Control
$('select[multiple] option').on('mousedown', function(e) {
    var $this = $(this),
        that = this,
        scroll = that.parentElement.scrollTop;

    e.preventDefault();
    $this.prop('selected', !$this.prop('selected'));

    setTimeout(function() {
        that.parentElement.scrollTop = scroll;
    }, 0);

    return false;
});
7

Here is a pure JS solution:

element.onmousedown= function(event) {
    //this == event.target
    event.preventDefault();
    var scroll_offset= this.parentElement.scrollTop;
    this.selected= !this.selected;
    this.parentElement.scrollTop= scroll_offset;
}
element.onmousemove= function(event) {
    event.preventDefault();
}

Look at the parent element(the select box) and record the vertical scroll offset before selecting/deselecting the option. Then reset it manually once you have performed the action.

The reasoning behind preventing default behavior for the mousemove event is because if you don't prevent it and you happen to click an option while moving the mouse, all options will become de-selected.

Luke
  • 5,567
  • 4
  • 37
  • 66
  • This works fine. A side effect is that other events may not fire. After adding this, I observed the following: (a) a disabled multiselect select field accepted the selection of options when clicking at an option. (b) An event that has been registered at 'input' did not fire any more. – S. Doe May 02 '23 at 16:00
1

Here's a solution that appears to work in Chrome FF and IE. It's not terribly pretty because it flashes a little bit when clicking.

var vals = [];
$("select[multiple]").click(function(e){
        var scroll_offset= this.scrollTop;
      var newVals = $(this).val();
    if (newVals.length === 1) {
        var index = vals.indexOf(newVals[0])
        if (index > -1) {
        vals.splice(index, 1);
      } else {
        vals.push(newVals[0])
      }
      $(this).val(vals);
      this.scrollTop = scroll_offset;
    }
});

jsfiddle

Tim Yonker
  • 403
  • 4
  • 7
1

For a pure js solution in chrome set the event listener on "onmouseup" instead. The event that resets the scroll to the first element is fired on the mouse up. So for example:

element.onmouseup = function(event) {
    event.preventDefault();
    var st = this.scrollTop;
    setTimeout(function () { element.scrollTop = st; }, 0);
};
Steve
  • 91
  • 1
  • 7
1

Short quick version

Created In Process

// this is for specyfic id  -> $('#WOStatusKey').

//below for all multiple select
$('select[multiple]').mousedown(e=>{
e.target.selected = !e.target.selected;
e.stopPropagation();
return false;
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<select multiple id="WOStatusKey" name="WOStatusKey">                
        <option value="1">Created</option>
        <option value="2">In Process</option>
        <option value="3">Bla bla bla</option>
    </select>
Adam111p
  • 3,469
  • 1
  • 23
  • 18
0

This worked better for me than the other suggestions, because it is vanilla JS and it behaves more similarly to the original still, for example allows quick selection of many items via the shift key.

element.onmousedown = function(event) {
    if (event.shiftKey) return;
    event.preventDefault();
    this.focus();
    var scroll = this.scrollTop;
    event.target.selected = !event.target.selected;
    this.scrollTop = scroll;
    // make sure that other listeners (like Vue) pick up on the change
    // even though we prevented the default.
    this.dispatchEvent(new Event("change"));
}
ZPiDER
  • 4,264
  • 1
  • 17
  • 17