jQuery option appendTo select moves to next option instead of select one - javascript

http://jsfiddle.net/j3oh6s3a/
For some reason appending options to a select tag doesn't select the selected='selected' attribute option, instead selects the next option in the list.
Please see the above jfiddle.
<select id="category">
<option value='1'>Categroy 1</option>
<option value='2'>Categroy 2</option>
<option value='3'>Categroy 3</option>
</select>
<select id="sub-category">
<option value='1' data-parentid='1'>Car1</option>
<option value='2' data-parentid='1'>Car2</option>
<option selected='selected' value='3' data-parentid='1'>Car3</option>
<option value='4' data-parentid='1'>Car4</option>
<option value='5' data-parentid='1'>Car5</option>
<option value='6' data-parentid='2'>Car6</option>
<option value='7' data-parentid='2'>Car7</option>
<option value='8' data-parentid='2'>Car8</option>
<option value='9' data-parentid='3'>Car9</option>
<option value='10' data-parentid='3'>Car10</option>
<option value='11' data-parentid='3'>Car11</option>
<option value='12' data-parentid='3'>Car12</option>
</select>
$(document).ready(function(){
var allsuboptions = $('#sub-category option').remove();
var selectedOptions = allsuboptions.filter(function () {
return $(this).data('parentid').toString() === $('#category').val().toString();
});
selectedOptions.appendTo('#sub-category');
});
In the above example Car3 should be selected, but Car4 is selected after appending options to the select.

This is a tricky (and interesting) question.
If you test the fiddle on different browsers you'll see that the selected value changes: Chrome (Car4), IE (Car3), Firefox (Car5). So I have made a slight change to your fiddle to "prove a theory". You can see the changes on this link: http://jsfiddle.net/j3oh6s3a/1/. I only added a log to the filter loop so I can see the selected element in each iteration:
if ($(this).is(":selected")) { console.log("Selected value = " + $(this).val()) };
Now this is what happens (or at least my theory): Once the selected element is removed from the list each browser will proceed however thinks adequate to determine the selected option. And in this case each browser will proceed in a different way:
As the selected option has been removed, Chrome will select automatically (by default) the first option of the remaining in the list (Car4). When this option is sent to the new list, it is automatically selected as it is newer than the previous selected option. The log is: 3, 4.
Internet Explorer does nothing, and copies each element the same way they are without caring about if they are selected or not. The original selected value will be the final selected value (Car3). The log is: 3.
Firefox will proceed like Chrome, but every time that the selected element is removed from the list, the first option of the remaining ones will be selected. The log is: 3, 4, 5, 6, 7, 8, 9, 10, 11, 12; but as the last option inserted in the list is 5, it will be the selected one.
I will check later to see if I can find any information to source this, but it will have to be tomorrow as it's a bit late here.

jQuery .remove and .append internally uses .removeChild and .appendChild methods to remove/insert the child elements.
Theory: removeChild/appendChild maintains the attributes but doesn't maintain the element's property (maintaining the selection state)
When you use removeChild and then appendChild to add the options back, the attributes are maintained, but the property of the element are not maintained. You can read more about .prop() vs .attr() here.
In summary, attributes are initial values defined in the HTML that are parsed to set as properties to the Element by the browser, however setting the attributes doesn't guarantee setting the property.
$(function() {
var categoryDD = document.getElementById('category');
var removedOptions = remove.call(categoryDD.options);
add.call(categoryDD, removedOptions);
});
function add(options) { //add all options
for (var i = 0; i < options.length; i++) {
this.appendChild(options[i]);
}
}
function remove() { //removes all options
var el, returnOpt = [];
while (this.length) {
el = this[0];
returnOpt.push(el.parentNode.removeChild(el));
}
return returnOpt;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<select id="category">
<option value='1'>Categroy 1</option>
<option value='2' selected="selected">Categroy 2</option>
<option value='3'>Categroy 3</option>
<option value='4'>Categroy 4</option>
<option value='5'>Categroy 5</option>
<option value='6'>Categroy 6</option>
</select>
Testing Results:
On testing the above snippet, IE 10 and FF yielded me the same result which is selecting the last option from the drop down, however chrome seems to be bugged as it always selected the next option from the original selection. The results from IE 10 and FF made a little sense as to "Not maintaining the state", however Chrome behavior seems like a bug.
Above is my theory based on my test case, however I couldn't find a legit reference that states the same.
Anyways, tryout below solutions for a consistent output.
Solution 1: Remove only options that are NOT equal to parentId.
$('#sub-category option').filter(function () {
return $(this).data('parentid').toString() !== $('#category').val().toString();
}).remove();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<select id="category">
<option value='1'>Categroy 1</option>
<option value='2'>Categroy 2</option>
<option value='3'>Categroy 3</option>
</select>
<select id="sub-category">
<option value='1' data-parentid='1'>Car1</option>
<option value='2' data-parentid='1'>Car2</option>
<option selected='selected' value='3' data-parentid='1'>Car3</option>
<option value='4' data-parentid='1'>Car4</option>
<option value='5' data-parentid='1'>Car5</option>
<option value='6' data-parentid='2'>Car6</option>
<option value='7' data-parentid='2'>Car7</option>
<option value='8' data-parentid='2'>Car8</option>
<option value='9' data-parentid='3'>Car9</option>
<option value='10' data-parentid='3'>Car10</option>
<option value='11' data-parentid='3'>Car11</option>
<option value='12' data-parentid='3'>Car12</option>
</select>
Solution 2: [based on your original answer] The solution is simple, just get the select value before removing the options and set the selection after using .append.
var $subcategory = $('#sub-category');
var selectedOption = $subcategory.val();
var allsuboptions = $subcategory.find('option').remove();
var selectedOptions = allsuboptions.filter(function() {
return $(this).data('parentid').toString() === $('#category').val().toString();
});
selectedOptions.appendTo('#sub-category');
$subcategory.val(selectedOption);
<select id="category">
<option value='1'>Categroy 1</option>
<option value='2'>Categroy 2</option>
<option value='3'>Categroy 3</option>
</select>
<select id="sub-category">
<option value='1' data-parentid='1'>Car1</option>
<option value='2' data-parentid='1'>Car2</option>
<option selected='selected' value='3' data-parentid='1'>Car3</option>
<option value='4' data-parentid='1'>Car4</option>
<option value='5' data-parentid='1'>Car5</option>
<option value='6' data-parentid='2'>Car6</option>
<option value='7' data-parentid='2'>Car7</option>
<option value='8' data-parentid='2'>Car8</option>
<option value='9' data-parentid='3'>Car9</option>
<option value='10' data-parentid='3'>Car10</option>
<option value='11' data-parentid='3'>Car11</option>
<option value='12' data-parentid='3'>Car12</option>
</select>

What you're not seeing is the difference between the "selected" property and the "selected" attribute.
If you put this at the end of your code you can see it:
// Attribute
console.log( $("#sub-category").find("[selected]").val() );
// Property
console.log( $("#sub-category").find(":selected").val() );
Your option with value "3" has the selected attribute, but not the property, it's the opposite for the option with value "4".
Whenever you add options inside a select, you must re-select the desired one:
$("#sub-category").find("[selected]").prop("selected", true);

Firefox selects the last element. This behavior is probably correct and explainable. In order to understand, please keep the following in mind:
jQuery append and remove methods process the elements one by one behind the scene.
The current state of an input element should be retrieved or set using the corresponding property, not attribute.
Expected Behavior (Firefox)
Removing all options from a select element as demonstrated in your example works as follows:
Car1 is removed (Car3 remains selected)
Car2 is removed (Car3 remains selected)
Car3 is removed. Since this is the selected element, removing it will cause the next element to become selected
Car4 is removed. Since this is the selected element, removing it will cause the next element to become selected
This will repeat until all options are moved from DOM to the memory. At this point the options will have the following properties:
// allsuboptions.each(function() { console.log(this.value, this.selected); });
value: 1, selected: false
value: 2, selected: false
value: 3, selected: true
value: 4, selected: true
...
value: 12, selected: true
There are 3 options with selected = true. When you add the options back to the select element, the browser sets the last selected element as the selected one.
Internet Explorer and Chrome
While the options are removed one by one, these two browsers do not update the selected element immediately (they possibly wait for JavaScript execution to finish). This causes the following discrepancies:
Internet Explorer does not immediately make the next option selected when currently selected option is removed. Therefore the removed options contain only one selected element.
Chrome seems to immediately make the next option selected when currently selected option is removed, but it does that only once. Therefore the removed options contain two selected elements.
In order to prove the point about immediate and deferred updates, here is a Fiddle containing:
The original code
A variation that forces the browser to update the select element each time an option is removed
http://jsfiddle.net/salman/j3oh6s3a/9/
Solution
As suggested in other answers, the correct workaround is to use a variable that references the currently selected option before removing the options. Something like:
var $selectedOption = $("#sub-category option").filter(function() {
return this.selected;
});
Once the options are re-inserted, you can check if that element was added back and select it again:
if ($selectedOption.parent().length) {
$selectedOption.prop("selected", true);
}
// or
$("#sub-category option").filter($selectedOption).prop("selected", true);

Your script is doing what you created it to do.
The reason car4 is selected, is because
<option selected='selected' value='3' data-parentid='1'>Car3</option>
is initially selected, then you are appending parentid='1' to the value which causes car4 to be the new selection.
What is the purpose of this script?

Related

how to get last selected option in multiple select in javascript [duplicate]

This question already has answers here:
How to get all selected values of a multiple select box?
(28 answers)
Closed 11 days ago.
I want to get the last selected <option> in a <select multiple> in javascript not jquery!
The last selected option means the last option selected by the user.
Not that the last option element in the select element!
I try:
<select multiple="multiple" onchange="changeEvent(this)">
function changeEvent(selectTag) {
console.log(selectTag.value);
}
I expected to get the last <option> selected
The value property of the select tag only returns the value of the selected option if the multiple attribute is not set. If the multiple attribute is set, you can use the options property of the <select> element to get an array of all the options and check which ones are selected.
Here's an updated version of the function with JavaScript:
function changeEvent(selectTag) {
let selectedOptions = [];
for (let i = 0; i < selectTag.options.length; i++) {
if (selectTag.options[i].selected) {
selectedOptions.push(selectTag.options[i].value);
}
}
console.log(selectedOptions);
}
<select multiple="multiple" onchange="changeEvent(this)">
<option value="option1">Option 1</option>
<option value="option2">Option 2</option>
<option value="option3">Option 3</option>
<option value="option4">Option 4</option>
<option value="option5">Option 5</option>
</select>
To get all the selected options, use selectedOptions on the selectTag.
For the 'last' option, use the default selectTag.value.
Remember that .value will only contain the last selected value, so if you'd un-select some value, the previous selected will be set as value of selectTag.value
function changeEvent(selectTag) {
const allSelectedValues = Array.from(selectTag.selectedOptions).map(t => t.value).join(', ');
console.log(`All selected: ${allSelectedValues}`);
console.log(`Previous: ${selectTag.value}`);
}
<select multiple="multiple" onchange="changeEvent(this)">
<option>foo</option>
<option>bar</option>
<option>foobar</option>
</select>

I know part of the value of a select option, I would like set that option with JS?

I would like to set an option in a select element based on the substring which will be contained in one of the option values. I have a solution but it seems quite convoluted to me.
I get the values of the options and put them in an array, loop through the values and check if the value includes a string I am looking for when it does I set that value as the select value. There must be an easier way!
I get the values of the options in the following way.
// Get the select element where you can select a procinve with a pull-down
var provincePullDown = document.querySelector(".select-provincie");
// Array containing the values of the select options
var optArray = Array.from(provincePullDown.options);
var optArrayValues = [];
optArray.forEach(el => optArrayValues.push(el.value));
I then loop over the values looking with a likely substring.
// Loop over the values in the select options
optArrayValues.forEach(function (el) {
// Look for the option containing the right province
if (el.includes(selectedProvince)) {
// Set the selected option from the select element
provincePullDown.value = el;
}
});
The option values look something like this sting (3) and the substring like this string.
I would like to know if there is an easier way as to me this seems an overly convoluted solution. And keeping maintenance in mind I would like an clear solution that I will still easily understand in 6 months.
The page is created by Drupal so I also control what html is outputted and the option values are inserted in the Drupal template.
Let me also state that I am not a fan of jQuery even though the project does load jQuery by default.
As querySelector() support any valid CSS selector, you can try with contains (*=) Attribute selector:
[attr*=value]
Represents elements with an attribute name of attr whose value contains at least one occurrence of value within the string.
var selectedProvince = 'province';
document.querySelector(".select-provincie option[value*='"+selectedProvince+"']").selected = true;
<select class="select-provincie">
<option value="prov-1">Province 1</option>
<option value="prov-2">Province 2</option>
<option value="province-test">Province 3</option>
<option value="prov-3">Province 4</option>
</select>
You can also use Template Literals for cleaner syntax:
var selectedProvince = 'province';
document.querySelector(`.select-provincie option[value*='${selectedProvince}']`).selected = true;
<select class="select-provincie">
<option value="prov-1">Province 1</option>
<option value="prov-2">Province 2</option>
<option value="province-test">Province 3</option>
<option value="prov-3">Province 4</option>
</select>
You can directly point to the option value if the regext is just a simple contain
const optToSelect = document.querySelector('option[value*=${SUBSTRING}]');
document.querySelector("select").selectedIndex = optToSelect.index;
You can use plain DOM methods with find and includes:
function updateOther(source) {
let value = source.value;
let sel = document.querySelector('.select-provincie');
sel.selectedIndex = [].find.call(sel.options, opt => opt.value.includes(value)).index;
}
<select onchange='updateOther(this)'>
<option value="1" selected>1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
</select>
<select class="select-provincie">
<option value="province1" selected>Province 1</option>
<option value="province2">Province 2</option>
<option value="province3">Province 3</option>
<option value="province4">Province 4</option>
</select>
But it's not fault tolerant: if a suitable value isn't found, it will throw an error (as do other answers). :-)
JQuery is interesting precisely when you want simplify solutions and ensure it is practical in terms of optimization. So I want to bring you a JQuery solution.
var selectedProvince = 'province3';
$(".select-provincie > option").each((index, elem) => {
if (elem.value == selectedProvince) {$(elem).attr('selected', true)};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="select_provincie">
<select class="select-provincie">
<option value="province1">Province 1</option>
<option value="province2">Province 2</option>
<option value="province3">Province 3</option>
<option value="province4">Province 4</option>
</select>
</div>

sort a dropdown list with added atrribute or id

Currently I am working on a site where I do not have access to the perl generated options of a drop down list. The drop downs are populated dynamically and not all options are available to all users.
The code I am able to work with is shown here.
<select class="fielddrop" name="PRIMARY_POS" size="1" style="width: 187px;" ></select>
PRIMARY_POS
populates each option that is able to be selected.
The actual output as seen when the page renders is
<select class="fielddrop" name="PRIMARY_POS" size="1" style="width: 187px;">
<option value="0">None Selected
<option value="155935">Option4
<option value="155934">Option3
<option value="155905">Option2
<option value="155933">Option1
<option value="155932">Option5
</select>
What I need to be able to do is set a sort order based on a hidden attribute that is assigned based on the text value
So in the above example. I need the drop downs ( Important as their are mulitple drop downs on the page ) to be able to be sorted by a not yet created attribute
So that the above code might then be
<option value="0">None Selected
<option sortvalue="5" value="155935">Option4
<option sortvalue="4" value="155934">Option3
<option sortvalue="3" value="155905">Option2
<option sortvalue="2" value="155933">Option1
<option sortvalue="1" value="155932">Option5
</select>
The sortvalue being set base don the Text value of the option select. So that a sortvalue of 5 would be assign to Option4. Just a smaple as the text will need to be assigned.
End result should be that the Drop down list now has a custom attribute of Sortvalue and the select drop down is now sorted by that value.
Once again, I can not directly change the attributes but can manipulate the results. Hope that was easy to follow, which I doubt :/
You can create an object where the keys are the text and values are sort order. Then loop over options and add attribute based on that map
var optsMap = {
"Option4": 5,
"Option5": 1
......
};
var $select = $('select[name=PRIMARY_POS]')
$select.find('option').attr('data-sortvalue', function(){
return optsMap[$(this).text()] ||0;
}).sort(function(a,b){
return +($(a).data('sortvalue')||0) - +($(b).data('sortvalue')||0);
}).appendTo($select);
You can then read the value using:
$select.change(function(){
alert($(this).find(':selected').data('sortvalue'));
})
If all you are needing is sorting and don't need attribute can remove one step
DEMO
Common practice is to prefix those "added attributes" with data. You could try something like this with jQuery, if I'm understanding you correctly.
Example fiddle: https://jsfiddle.net/30cvudz8/7/
<select class="my-select">
<option data-sort-value="3" value="1">Option 1</option>
<option data-sort-value="5" value="2">Option 2</option>
<option data-sort-value="4" value="3">Option 3</option>
<option data-sort-value="1" value="4">Option 4</option>
<option data-sort-value="2" value="5">Option 5</option>
</select>
var optionList = new Array();
$('select.my-select option').each(function() {
optionList[optionList.length] = $(this).attr('data-sort-value')+'::'+$(this).val();
});
optionList.sort(); // sort it
var newOptionList = '';
for(var i = 0; i < optionList.length; i++) {
// recreate option
var parts = optionList[i].split('::');
newOptionList += '<option value="'+parts[1]+'" data-sort-value="'+parts[0]+'">Option '+parts[1]+'</option>';
}
// wipe and repopulate the select list
$('select.my-select').html(newOptionList);
To add an attribute (like data-sort-value) after you have a select list, you can do something like this:
$('select.original option').each(function() {
var sortingValue = getSortingValueFromText($(this).text());
$(this).attr('data-sort-value', sortingValue);
});

Mutual exclusion for <option>s in a <select>?

I need to combine the functionality of single selection and multiple select into a single control. Specifically, I have a number of options. The first one is mutually exclusive to the others. So, if I select the first one, it needs to uncheck all the others. If one of the others is selected, it must uncheck the first one (if selected). The other options should have no effect on each other.
<select id="myGroup" data-native-menu="false" multiple="multiple" >
<option value="" >Select Group(s)</option>
<option value="-1" selected="selected" >I am alone</option>
<option value="1" >I am not alone 1</option>
<option value="2" >I am not alone 2</option>
<option value="3" >I am not alone 3</option>
</select>
I installed an onchange() handler. So, I know when selections are made. But I can't seem to tell which option just got selected. So, in the example above, if the user select option 3, $(this).val() becomes -1,3. How can I tell that is was "3" that just got selected?
The only thing that I've come up with so far is to keep an array of selected options and then diff the arrays when a new option is selected.
$('select[id=myGroup]').change(function() {
// At this point, I know the sum total of what's been selectec.
// But I don't know which one just got added to the list.
// I want logic that says:
// if -1 just got added, then unselect all the others
// if something else was just added, make sure that -1 is not selected
var selected = $(this).val();
alert(JSON.stringify(selected));
});
Is there a better way?
You only need to keep state for the first option, not all of them:
var firstOption = $("#myGroup > option[value=-1]");
var firstSelectedBefore = firstOption.prop("selected");
$("#myGroup").on("change", function(event) {
if (firstOption.prop("selected") && this.selectedOptions.length > 1) {
if (firstSelectedBefore) { // non-first option just selected
firstOption.prop("selected", false);
} else { // first option just selected
$(this).find("option:not([value=-1])").prop("selected", false);
}
}
firstSelectedBefore = firstOption.prop("selected");
});

Removing and adding options from a group of select menus with jQuery

This is a little more complicated than the title makes it out to be, but here are the essential business rules:
There are three select menus on the
page, each filled with the same
options and values.
There will always be three select
menus.
There will always be the same number
of options/values in each select
menu.
Selecting a question in any of the
menus will remove that question as an option from
the other two menus.
Re-selecting a different question
from any of the menus will bring
back the question that was
previously removed from the other
two menus at the index it was at previously.
I've tried this a few different ways, and the thing that is killing me is number 5. I know that it wouldn't be inserted at the exact index because some questions may have already been removed, which would reorder the index. It basically needs an insertBefore or insertAfter that puts it in the same "slot".
Even if you don't post any code, some thoughts on how you might approach this would be extremely helpful. The select menus and jQuery look something like this, but I've had numerous tries at it in different variations:
jQuery:
$(function() {
$(".questions").change(function() {
var t = this;
var s = $(t).find(":selected");
// Remove, but no "insert previously selected" yet...
$(".questions").each(function(i) {
if (t != this) {
$(this).find("option[value=" + s.val() + "]").remove();
}
});
});
});
HTML:
<select name="select1" class="questions">
<option value="1">Please select an option...</option>
<option value="2">What is your favorite color?</option>
<option value="3">What is your pet's name?</option>
<option value="4">How old are you?</option>
</select>
<select name="select2" class="questions">
<option value="1">Please select an option...</option>
<option value="2">What is your favorite color?</option>
<option value="3">What is your pet's name?</option>
<option value="4">How old are you?</option>
</select>
<select name="select3" class="questions">
<option value="1">Please select an option...</option>
<option value="2">What is your favorite color?</option>
<option value="3">What is your pet's name?</option>
<option value="4">How old are you?</option>
</select>
Don't remove the elements, hide them. With removing, you are causing you a lot more problems than necessary. This works for me:
$(function() {
$('select.questions').change(function() {
var hidden = [];
// Get the values that should be hidden
$('select.questions').each(function() {
var val = $(this).find('option:selected').val();
if(val > 0) {
hidden.push($(this).find('option:selected').val());
}
});
// Show all options...
$('select.questions option').show().removeAttr('disabled');
// ...and hide those that should be invisible
for(var i in hidden) {
// Note the not(':selected'); we don't want to hide the option from where
// it's active. The hidden option should also be disabled to prevent it
// from submitting accidentally (just in case).
$('select.questions option[value='+hidden[i]+']')
.not(':selected')
.hide()
.attr('disabled', 'disabled');
}
});
});
I made a small change to your HTML also, I denoted an option that should always be visible with a value of 0. So the valid options go from 1 to 3.
Here's a working example, tell me if I misunderstood you:
http://www.ulmanen.fi/stuff/selecthide.php
I was working on a solution of this recently and modified this code to remove rather than disable/hide. For my solution it was required (I'm also using UI to style the select elements). Here's how I did it:
// Copy an existing select element
var cloned = $('select[name="select1"]').clone();
// Each time someone changes a select
$('select.questions').live('change',function() {
// Get the current values, then reset the selects to their original state
var hidden[];
$('select.questions').each(function() {
hidden.push($(this).val());
$(this).html(cloned.html());
});
// Look through the selects
for (var i in hidden) {
$('select.questions').each(function() {
// If this is not the current select
if ((parseInt(i)) != $(this).parent().index()) {
// Remove the ones that were selected elsewhere
$(this).find('option[value="'+hidden[i]+'"]').not('option[value="0"]').remove();
} else {
// Otherwise, just select the right one
$(this).find('option[value="'+hidden[i]+'"]').not('option[value="0"]').attr('selected','selected');
}
});
}
});

Categories

Resources