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.
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
Inherited some Javascript code (which is paraphrased in the Fiddle for ease of reproduction), which is filtering Select Options based on a selection in a previous Select.
When the page loads, a clone of the Select is made to hold the original values of the 2nd Select element.
When the user makes a Selection in Select element 1, Select element 2 is restored with the original values (as the user might make a different selection in element 1), and then it filters the list.
When the user makes a selection in Select 2, we remove the "Select..." element from the list... But In IE10 the selected element then becomes the option below it.
This only happens in IE10 (IE8/9/11 are all ok, as are latest versions of Chrome and FF)
It looks like an issue with the .clone() element being restored and the "Select..." option being removed.
Here's a fiddle to check it...
cloneModel = $("#model").clone();
$("#make").change(function(){
restoreModels();
removeNoOption("#make");
$("#make").hide();
$("#make").show();
filterModels($("#make option:selected").val());
$("#model").show();
})
function modelChange(){
removeNoOption("#model");
$("#model").hide();
$("#model").show();
}
function removeNoOption(element){
$(element + " option").each(function(){
if($(this).val() == "-"){
$(this).remove();
}
})
}
function restoreModels(){
$("#model").unbind();
$("#model").replaceWith(cloneModel);
cloneModel = $("#model").clone();
$("#model").change(modelChange);
$("#model").hide();
}
function filterModels(make){
$.each(models, function(key, value) {
if(make != value.make){
$("#model option[value='"+value.model+"']").remove();
}
});
}
Anyone ever seen this before or know what could be happening?
I'm using the code from the accepted answer here
How do you limit options selected in a html select box?
to count the selected options in a 'select multiple' menu:
var last_valid_selection = null;
$("#select_options").change(function(event) {
if ($(this).val().length > 10) {
$(this).val(last_valid_selection);
} else {
last_valid_selection = $(this).val();
$("#select_options_text").text("Please select at least one, and up to ten options. You have currently selected "+$(this).val().length);
}
});
The menu is divided into six optgroups. When I hit 10 selections I can no longer make selections, as expected. But I can also no longer use CTRL+click on selected options to deselect them.
If I remove all optgroups, the menu functions correctly. It also functions correctly with one and two optgroups. It only seems to be when a third optgroup is added that the problem described above appears.
I have tested in Chrome and Firefox and the problem occurs in both.
Problem
You have duplicate options, so when try to restore the last selection by calling $(this).val(last_valid_selection), you could be selecting more than one value than you actually want (i.e. you end up selecting more than 10).
For example, you have more than one Biochemistry, so when last_valid_selection contains one instance of Biochemistry, all the duplicate Biochemistry options will be selected.
Solution
Use a different way of remembering the last valid selections.
Here I present a solution using data attribute and individually store whether or not an option has been previously selected.
function save_selected(select){
$(select).find("option").each(function(){
var t = $(this);
t.data("last-selected", t.is(":selected"));
});
};
function load_selected(select){
$(select).find("option").each(function(){
var t = $(this);
t.attr("selected", t.data("last-selected"));
});
};
$("#select_options").change(function(event) {
if ($(this).val().length > 10) {
load_selected(this);
} else {
save_selected(this);
}
});
Using this method, each individual option element has its own "last selected" state stored in its own data attribute. There would be no duplicate conflicts.
Demo: https://jsfiddle.net/alan0xd7/gzdrL5wu/12/
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).
I have a dropdown select list on my page of class="TypeFilter".
I have a jQuery event that fires when a value in that list is selected.
$(".TypeFilter").change(function()
{
// Extract value from TypeFilter and update page accordingly
));
I now have to add another list to the page, and I want to implement functionality which will prevent the .change(function() from running unless both are selected.
In both lists the first option in the list is some text instructing the user to select one of the items, so I was thinking of just writing some logic to test that both lists have a selected index greater than 0.
I think this is a touch unclean though, especially considering that other pages that have a TypeFilter use the same logic.
Is there any nifty functionality in jQuery that can do this?
edit I should specify that the user needs to be able to update the page by selecting either dropdown, so I can't put the onchange on the second element and test that the first element has a selected value, as suggested in one of the answers
If you bind the same event to all dropdowns, you can get a collection of all the dropdowns and check that all of them are selected. Example:
$('.Dropdown').change(function(){
var elements = $('.Dropdown');
if (
elements.filter(function(){
return this.selectedIndex > 0;
}).length == elements.length
) {
// all dropdowns are selected
}
});
As you partly mention, put the onchange on the second element and test that the first element has a selected value before you fire off any logic.
Use bind instead, and as the eventdata, send a function that checks that either that both are selected or that the other is selected. Untested code:
function checker() {
// test your conditions
}
$(".TypeFilter").bind('change', {test: checker}, function(event)
{
if (event.data.test && event.data.test()) {
// Extract value from TypeFilter and update page accordingly
}
));
This way the other pages that use the same function will not notice any changes.