I have a web application I\'m working on that requires a HTML multiple select element operates like the control key is held down at all times. I.e. click on an option will
The above solutions all have the major downside of not supporting selections in the checkbox, which is actually the reason I'd use <select>
-boxes over checkbox-inputs in the first place. Initially I visited this page to find a built-in way to fix this, but considering the above answers, I'll just write my own code.
The following code also has the advantage that it doesn't update on mouseup as opposed to the current top answer by iambriansreed, but on mousedown, which is the default behaviour in browsers.
Just to help out future googlers, my code below:
// Copyright (c) 2018 Joeytje50. All rights reserved.
// This code is licenced under GNU GPL3+; see <http://www.gnu.org/licenses/>
// Source code and most recent version:
// https://gist.github.com/Joeytje50/2b73f9ac47010e7fdc5589788b80af77
// select all <options> within `sel` in the range [from,to]
// change their state to the state sel.options[from] is in,
// ie. change it to the current state of the first selected element
function selectRange(sel, from, to) {
var toState = sel.options[from].selected;
// make sure from < to:
if (from > to) {
var temp = from;
from = to;
to = temp;
}
if (!(sel instanceof HTMLSelectElement)) {
throw new TypeError('selectRange requires a single non-jQuery select-element as first parameter');
}
// (de)select every element
for (var i=from; i<=to; i++) {
sel.options[i].selected = toState;
}
}
$(function() {
$('.addSelect').data('select-start', 0); // default selection start
$('.addSelect').on('mousedown', function(e) {
$(this).focus();
// clicking on the edge of the <select> shouldn't do anything special
if (!$(e.target).is('option')) return;
// if Ctrl is pressed, just let the built-in functionality take over
if (e.ctrlKey) return;
// keep everything selected that's not affected by the range within a shift-click
if (e.shiftKey) {
var fromIdx = $(this).data('select-start')
selectRange(this, $(this).data('select-start'), e.target.index);
e.preventDefault();
return false;
}
// save the starting <option> and the state to change to
$(this).data('select-start', e.target.index);
e.target.selected = !e.target.selected;
e.preventDefault();
// save a list of selected elements, to make sure only the selected <options>
// are added or removed when dragging
var selected = [];
for (var i=0;i<this.selectedOptions.length;i++) {
selected.push(this.selectedOptions[i].index);
}
$(this).data('selected', selected);
$(this).children('option').on('mouseenter', function(e) {
var sel = this.parentElement;
// first reset all options to the original state
for (var i=0;i<sel.options.length;i++) {
if ($(sel).data('selected').indexOf(i) == -1) {
sel.options[i].selected = false;
} else {
sel.options[i].selected = true;
}
}
// then apply the new range to the elements
selectRange(sel, $(sel).data('select-start'), e.target.index);
});
});
// clean up events after click event has ended.
$(window).on('mouseup', function() {
$('.addSelect').children('option').off('mouseenter'); // remove mouseenter-events
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<select class="addSelect" multiple size="10">
<option value="0">Foo0</option>
<option value="1">Bar1</option>
<option value="2">Baz2</option>
<option value="3">Lol3</option>
<option value="4">Heh4</option>
<option value="5">Hey5</option>
<option value="6">Meh6</option>
<option value="7">Xcq7</option>
<option value="8">Hmm8</option>
<option value="9">End9</option>
</select>
Feel free to use this code by simply copying it to any project you'd like to use it on.
Try this out. You can store the option values in an object and use the click action to update the object then apply the changes to the select.
Demo
http://jsfiddle.net/iambriansreed/BSdxE/
HTML
<select class="select-toggle" multiple="multiple">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
</select>
JavaScript
$('.select-toggle').each(function(){
var select = $(this), values = {};
$('option',select).each(function(i, option){
values[option.value] = option.selected;
}).click(function(event){
values[this.value] = !values[this.value];
$('option',select).each(function(i, option){
option.selected = values[option.value];
});
});
});
Had to solve this problem myself and noticed the bugged behavior a simple interception of the mousedown and setting the attribute would have, so made a override of the select element and it works good.
Note: This code does fix buggy behavior by replacing the select element in the DOM. This is a bit agressive and will break event handlers you might have attached to the element.
window.onmousedown = function (e) {
var el = e.target;
if (el.tagName.toLowerCase() == 'option' && el.parentNode.hasAttribute('multiple')) {
e.preventDefault();
// toggle selection
if (el.hasAttribute('selected')) el.removeAttribute('selected');
else el.setAttribute('selected', '');
// hack to correct buggy behavior
var select = el.parentNode.cloneNode(true);
el.parentNode.parentNode.replaceChild(select, el.parentNode);
}
}
<h4>From</h4>
<div>
<select name="sites-list" size="7" multiple>
<option value="site-1">SITE</option>
<option value="site-2" selected>SITE</option>
<option value="site-3">SITE</option>
<option value="site-4">SITE</option>
<option value="site-5">SITE</option>
<option value="site-6" selected>SITE</option>
<option value="site-7">SITE</option>
<option value="site-8">SITE</option>
<option value="site-9">SITE</option>
</select>
</div>
You may want to consider a simpler solution, like using a list of checkboxes inside a div whose overflow property is set to scroll. That might work out better for you. Getting a drop down to do what you've asked is a bit involved.
See this for example:
label{display:block;}
#container{height:100px;width:200px;overflow:scroll;}
<div id="container">
<label><input type="checkbox" name="test" value="1" />Option 1</label>
<label><input type="checkbox" name="test" value="2" />Option 2</label>
<label><input type="checkbox" name="test" value="3" />Option 3</label>
<label><input type="checkbox" name="test" value="4" />Option 4</label>
<label><input type="checkbox" name="test" value="5" />Option 5</label>
<label><input type="checkbox" name="test" value="6" />Option 6</label>
<label><input type="checkbox" name="test" value="7" />Option 7</label>
<label><input type="checkbox" name="test" value="8" />Option 8</label>
<label><input type="checkbox" name="test" value="9" />Option 9</label>
<label><input type="checkbox" name="test" value="10" />Option 10</label>
<label><input type="checkbox" name="test" value="11" />Option 11</label>
<label><input type="checkbox" name="test" value="12" />Option 12</label>
</div>