Unable to deselect 'select multiple' options with selection counter and optgroups - javascript

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/

Related

Dynamically disabling <option> elements based on amount selected

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)

IE10 Select option indexing issue

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?

Removing an option from all other chosen multiple dropdowns, when selected on a particular dropdown

I have attached a JS Fiddle link here:
http://jsfiddle.net/p0b4j5o8/18/
If you check the fiddle link, you will see that I have written JavaScript/jQuery code (a function named add_mitigator()) in the HTML section.
When I used to write the code in the JavaScript section, Firebug used to trigger an error stating function add_mitigator() isn't defined.
But when I wrote the same in the HTML section instead, it runs with no problem. I am not much accustomed with JS Fiddle. How can I write a function in function_name() format in the fiddle?
In my JS Fiddle, there is a dropdown (chosen multiple) with options 1 to 10. When I click on the add_mitigator link, it creates/appends a new chosen dropdown.
Suppose I have three dropdowns, then if I select 2 from dropdown 1, then the other dropdowns will have 2 removed from their options. When I will unselect the 2 from the first dropdown, then 2 will again be available in the other two dropdowns. This should act vice-versa. When 2 is selected from any dropdown, then it is unavailable for all other dropdowns.
How can I achieve this?
By calling the function below on the select box change event the options will be disabled/enabled as per your requirements.
function disableSelectedValues() {
var cssOptions = [];
$(".input_field.criteria_countries option").removeAttr("disabled");
$.each($(".input_field.criteria_countries"), function(index, select) {
cssOptions = [];
var values = $(select).val();
if (values) {
for (var i = 0; i < values.length; i++) {
cssOptions.push("option[value=" + values[i] + "]");
}
}
console.log("test");
if (cssOptions.length) {
// disable all options with the selected values
$(".input_field.criteria_countries "
+ cssOptions.join(",.input_field.criteria_countries ")).attr("disabled", true);
// enable all options with the selected values for the current select
$(select).find(cssOptions.join()).removeAttr("disabled");
}
});
}
With .chosen you can listen to onchange events with .chosen().change();.
In the .chosen().change() event you then need to tell all of the select boxes to update once a value has changed. By using .trigger("chosen:updated"); on the select box selectors whenever a change event is fired, the select boxes will update.
NOTE that you will need to call the disableSelectedValues() function whenever you add a select box to have its options disabled on creation.
http://jsfiddle.net/p0b4j5o8/22/

Selecting and Deselecting multiple options from list without keyboard

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.

How to trigger an event ONLY if both dropdowns are selected?

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.

Categories