I've hacked html's < select multiple > with javascript, according to my customer's specifications:
clicking an item only toggles that item's selected status.
other selected items stay selected.
The little bit of javascript remembers all selected values.
When the user clicks, only the option he clicks will be selected.
The javascript selects the options he remembers.
However, it causes a flicker effect. I doubt there is any solution for this, but I felt I had to ask just in case, does anyone know of a way to delay the rendering, or any other solution to accomplish this, without a flicker?
Best regards.
EDIT: here is the code
var choices=new Array();
function prepmulti(){
var m=document.querySelectorAll('select');
for(var i=0;i<m.length;i++)
if(m[i].id!=''){
m[i].onclick=toggle;
choices.push(new Array());
}
}
function toggle(){
var sel, x;
for(var i=0; i<this.options.length;i++)
if(this.options[i].selected){
sel=i;
break;
}
if((x=choices[this.id].indexOf(sel))<0)
choices[this.id].push(sel);
else{
choices[this.id].splice(x, 1);
this.options[sel].selected=false;
}
for(i=0;i<choices[this.id].length;i++)
this.options[choices[this.id][i]].selected=true;
}
<body onload='prepmulti();'>
The select multiples have ids 0, 1, 2, etc... If you give them other ids, the code must be modified a little.
The flicker you see is from it changing the selected option(s) then repainting - unfortunately I don't think this is avoidable in most browsers.
In Chrome you can bind onmousedown on the select, use preventDefault and then set the event target's selected to the opposite of what it is.
var m=document.querySelectorAll('select');
for(var i=0;i<m.length;i++)
{
m[i].onmousedown = function(e) {
e.preventDefault();
e.target.selected = !e.target.selected;
};
}
http://jsfiddle.net/VDnQK/
This only works in Chrome, though. IE supports preventDefault for select but e.target returns the select rather than the option element.
FireFox doesn't seem to support preventDefault for select (at least, I can't get it to work).
Related
I thought this would be a simple hack, but I've now been searching for hours and can't seen to find the right search term. I want to have an ordinary multiple select box (<select multiple="multiple">) except I don't want the user to have to hold down the control key to make multiple selections.
In other words, I want a left click to toggle the <option> element that's under the cursor without changing any of the others. In other other words, I want something that looks like a combo list box but behaves like a group of check boxes.
Can anybody suggest a simple way to do this in Javascript? Thanks.
Check this fiddle: http://jsfiddle.net/xQqbR/1022/
You basically need to override the mousedown event for each <option> and toggle the selected property there.
$('option').mousedown(function(e) {
e.preventDefault();
$(this).prop('selected', !$(this).prop('selected'));
return false;
});
For simplicity, I've given 'option' as the selector above. You can fine tune it to match <option>s under specific <select> element(s). For ex: $('#mymultiselect option')
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.
jsFiddle: http://jsfiddle.net/51p7ocLw/
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>
techfoobar's answer is buggy, it unselects all options if you drag the mouse.
Sergio's answer is interesting, but cloning and removing events-bound to a dropdown is not a nice thing.
Try this answer.
Note: Doesn't work on Firefox, but works perfectly on Safari/Chrome/Opera. (I didn't test it on IE)
EDIT (2020)
After 5 years since my original answer, I think best practice here is to replace the dropdown with checkboxes. Think about it, that's the main reason why checkboxes exist in the first place, and it works nicely with old browsers like IE & modern mobiles without any custom JS to handle all the wacky scenarios.
Necromancing.
The selected answer without jQuery.
Also, it missed setting the focus when an option is clicked, because you have to do this yourself, if you write e.preventDefault...
Forgetting to do focus would affect CSS-styling, e.g. bootstrap, etc.
var options = [].slice.call(document.querySelectorAll("option"));
options.forEach(function (element)
{
// console.log("element", element);
element.addEventListener("mousedown",
function (e)
{
e.preventDefault();
element.parentElement.focus();
this.selected = !this.selected;
return false;
}
, false
);
});
I had same problem today, generally the advice is to use a list of hidden checkboxes and emulate the behavior via css, in this way is more easy to manage but in my case i don't want to modify html.
At the moment i've tested this code only with google chrome, i don't know if works with other browser but it should:
var changed;
$('select[multiple="multiple"]').change(function(e) {
var select = $(this);
var list = select.data('prevstate');
var val = select.val();
if (list == null) {
list = val;
} else if (val.length == 1) {
val = val.pop();
var pos = list.indexOf(val);
if (pos == -1)
list.push(val);
else
list.splice(pos, 1);
} else {
list = val;
}
select.val(list);
select.data('prevstate', list);
changed = true;
}).find('option').click(function() {
if (!changed){
$(this).parent().change();
}
changed = false;
});
Of course suggestions are welcome but I have not found another way
I have provided a user with an interface where they can select from 30 options(hardcoded), displayed in a bootstrap dualListBox.
This works perfectly however I would like to set the max amount of options that can be selected to 10.
After which, all the options that could have been selected on the left side, become disabled.
Should the user remove a selected option, the others can then become available for selection again.
I realize that I will be using jquery to achive this, however I am unsure as to how I will count the amount selected and how I will target remaining selections to disable or make them available again.
Is there any advice on how to correctly solve this problem?
The plugin I am using for the dual listbox can be found here:
Bootstrap dualListBox
First I would bind a change event, then I would check if the number of selected items is equal or exceeds then max number of elements, if so I will disable it.
If I understand your question correct it would be something like:
$('#selector-id').on('change', function(e){
var selectedElements = $(this).val();
if(selectedElements && selectedElements.length >= 10){
$(this).prop('disabled', true);
} else {
$(this).prop('disabled', false);
}
});
(This is not tested code)
I am trying to load options into a select element when the user focuses.
Here is the jquery code I'm using:
$('select').focus(function() {
var $this = $(this);
if (!$this.data('hasitems')) {
var selectedValue = $this.val();
$this.empty()
.append($('<option></option')
.attr('value','')
.text('Loading...'));
// This would usually be the result of an AJAX call
$this.empty()
.data('hasitems','true');
$.each(['a','b','c','d'], function(index,item) {
$this.append($('<option></option>')
.attr('value',item)
.text(item)
.prop('selected', selectedValue == item));
});
}
});
Here is the fiddle:
http://jsfiddle.net/agnnC/
The solution almost works... except in Firefox (and sometimes not in Chrome either, although the fiddle I put together does appear to work).
The problem in Firefox is that when the user clicks on the select element, the currently selected value is not remembered and is changed to one of the new values in the drop down.
Can anyone think of a workaround?
I think the issue is that the selected attribute is not really a property the way you are using it. Per the MDN doc:
selected
If present, this Boolean attribute indicates that the option is
initially selected. If the element is the descendant of
a element whose multiple attribute is not set, only one single
of this element may have the selected attribute.
Note, it talks about "initially selected" only, not real time changes. It also refers to selected only as an attribute, not a property.
The W3C spec for the option element also talks only about pre-selecting an option, not about making live changes using the selected attribute.
Once the select and options are live, the .selectedIndex property on the <select> object controls which option is selected in real time for single selection select elements.
In support of this theory, if you change to setting the saved item with .selectedIndex as shown below, the problem goes away:
$('select').focus(function() {
var $this = $(this);
if (!$this.data('hasitems')) {
var selectedValue = $this.val();
$this.empty()
.append($('<option></option')
.attr('value','')
.text('Loading...'));
// This would usually be the result of an AJAX call
$this.empty()
.data('hasitems','true');
$.each(['a','b','c','d'], function(index,item) {
$this.append($('<option></option>')
.val(item)
.text(item));
if (selectedValue == item) {
$this.prop("selectedIndex", index);
}
});
}
});
Working demo: http://jsfiddle.net/jfriend00/4333d/
Firefox can't change the focus option when select is opening, but you can change before it open, change the event to mousedown it working Fiddle
$('select').mousedown(function() {
// ...
});
I am having a weird issue regarding the dropdown menu.
I have
company.prototype.buildTable=function(){
var thisObj=this;
tableTD = createElement('td');
tableTD.onclick=function(){thisObj.edit(this);}
this.table.appendChild(tableTD); //append td to the table
// more td
}
company.prototype.edit = function(thisRow) {
$thisRow=$(thisRow);
var menu=document.createElement('select');
menu.className='menu';
//employees is an array containing employee info
for(var i=0; i<employees.length; i++){
var option=document.createElement('option');
option.className='option';
option.innerHTML=employees[i].name;
option.value=employees[i].email;
menu.appendChild(option);
}
$thisRow.append(selectMenu);
}
I can see a dropdown menu in my td. However, when I click the menu, I had to hold my mouse to
keep options open otherwise the options menu will close. Even if I hold my mouse and select one option,
the dropdown menu value wont' change (it still show the first option in the select menu). I hope I explain my issue well.
Can anyone help me with this weird issue?
IIRC, there are some weird cross browser issues with appending options as DOM elements. The preferred way to add options is simply:
menu.options[i] = new Option(employees[i].name, employees[i].email);
EDIT:
Do not to use appendChild on select and do not use innerHTML on select elements. TL;DR: HTMLSelectElement is special and has quirks in IE
I have the following list that is present to the user
<select id="regions" multiple="multiple">
<option value='us-west' selected="selected">US West Coast</option>
<option value='us-east' selected="selected">US East Coast</option>
<option value='eur' selected="selected">Europe</option>
<option value='asia' selected="selected">Asia</option>
</select>
As shown above, all items are selected.
I was trying to implement a jquery function where if the user clicks an option on the list only the clicked item (let's say Europe) will be removed, while the rest of the options are selected. I know that this is possible by holding down "CTRL" and clicking, but is there a way where all the user has to do is to click?
I tried this function:
<script type="text/javascript">
function(){
$("#regions options:selected").removeAttr("selected");
};
</script>
So it took me a while, but I think I have the functionality you're looking for.
Check this fiddle
The idea is, create an array to keep track of what's selected. The natural behaviour of a multiple select box if you only click, is to deselect all other selections and select what you clicked on.
Therefore, instead of being concerned with what's in the selectbox, we see which option is clicked on (on mousedown) and add it to our personal list of what's selected, then update the select box to reflect whats in our list (on mouseup).
This prevents the user from dragging to select multiple items in the selectbox, but being able to do that would probably just mess it up.
var selection = [];
$('#the_box > option').mousedown(function(){
if(selection.length < 1){
selection.push($(this).val());
} else {
var found = 0;
for(i = 0; i < selection.length; i++) {
if(selection[i] == $(this).val()){
selection.splice(i, 1);
found = 1;
break;
}
}
if(found === 0){
selection.push($(this).val());
}
}
});
$('#the_box').bind('mouseup mouseout', function(){
$(this).children('option').each(function(){
$(this).removeAttr('selected');
});
$(this).children('option').each(function(){
for(i = 0; i < selection.length; i++){
if($(this).val() == selection[i]){
$(this).attr('selected','selected');
}
}
});
});
You would however, be able to do this exact thing with just a list or set of divs, instead of using the native <select> tags, which would actually be easier if you're using this method (due to not having to work around the default behavior of the selectbox) :P
You can use:
$('option').click(function(){
$(this).removeAttr('selected', 'selected')
.siblings().attr('selected', 'selected');
});
What this function does, is to remove current selection state of the clicked item, but re-selecting the entire siblings set.
See this fiddle.
Update: Since by using keyboard, we can send another signal (another flag) to the control, we still need to simulate it somehow using mouse. We can for example assume that right-click is like Ctrl+click and left click is simply a click. However, right-click is not a good pattern in web and not that common.
Another option could be to redefine our business to make the control behave differently (for example, on mouse click, if the object is already selected, then deselect it, and if it's not selected, add it to the whole collection of selected options). But the disadvantage of this approach is that users get lost when using your control, because it gets far from normal HTML controls with which they have interaction on a daily basis. In other words, changing the behavior of a control is not a good solution regarding UX.
This is by no means the perfect solution but you are welcome to try it.
To attach the function (note that this allows a callback function to be fired after selecting items .. for example if you want to populate another list based on the selection)
$('#mySelect').click(function () {
singleClickList(this, myCallbackFunction);
});
The function:
function singleClickList(mythis, callback) {
var selected = $(mythis).data('selected');
if (selected == undefined) selected = [];
var scrollTop = mythis.scrollTop;
var current = $(mythis).val();
var index;
if (current != null) {
if (current.length > 0) {
index = $.inArray(current[0], selected);
if (index >= 0)
selected.splice(index, 1);
else
selected.push(current[0]);
}
$(mythis).val(selected);
$(mythis).data('selected', selected);
mythis.scrollTop = scrollTop;
if (callback) callback();
}
}
Note that this requires jquery, and makes use of the jquery .data method to store the array of selected items.