I am trying to make an HTML Select control display a different set of strings when clicked (or opened) than what is displayed after an item is selected. For example, when opened I want to see "one", two", "three" displayed as choices. But if the user selects two, I want "2" to be displayed as the selected item. My onclick handler reloads the Select options list with the long version of the strings and the onchange handler repopulates the control with the short strings and then re-selects the selected item. This works in Firefox, but not in IE, Safari nor Chrome. It's been almost 10 years since I last had the pleasure of coding in JavaScript. Any help would be appreciated. Here's my code:
var selectedIndex = -1;
function onChanged() {
//once selected, replace verbose with terse forms
var myList = document.getElementById("myList");
selectedIndex = myList.selectedIndex;
var optionArray = ["One|1", "Two|2", "Three|3"];
myList.options.length = 0;
for (var option in optionArray) {
var pair = optionArray[option].split("|");
var newOption = document.createElement("option");
newOption.value = pair[1];
newOption.innerHTML = pair[1];
myList.options.add(newOption);
}
myList.selectedIndex = selectedIndex;
}
function onClicked() {
var myList = document.getElementById("myList");
var optionArray = ["1|One", "2|Two", "3|Three"];
myList.options.length = 0;
for (var option in optionArray) {
var pair = optionArray[option].split("|");
var newOption = document.createElement("option");
newOption.value = pair[1];
newOption.innerHTML = pair[1];
myList.options.add(newOption);
}
if (selectedIndex > -1)
myList.selectedIndex = selectedIndex;
}
<select id="myList" onchange="onChanged()" onclick="onClicked()">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
An alternate approach would be to have each option store the full version of the text, and only change the displayed text of the selected item to the abbreviated version upon selection.
(function() {
var valueMap = {
"1": "One (1)",
"2": "Two (2)",
"11": "Eleven (11)",
"ITAR": "International Traffic in Arms Regulation (ITAR)",
"ACA": "Affordable Care Act (ACA)",
"FUBAR": "Fluffed Up Beyond All Recognition (FUBAR)"
};
var myList = document.getElementById("myList");
for (var prop in valueMap) { // populate the dropdown from our object
var opt = document.createElement("option");
opt.value = prop;
opt.text = valueMap[prop];
myList.add(opt);
}
myList.selectedIndex = -1; // nothing selected by default
myList.addEventListener("change", function() {
this.options[this.selectedIndex].text = this.options[this.selectedIndex].value;
this.blur();
});
myList.addEventListener("mousedown", function() {
if (this.selectedIndex > -1) {
var newValue = valueMap[this.options[this.selectedIndex].value];
if (this.options[this.selectedIndex].text !== newValue) {
this.options[this.selectedIndex].text = newValue;
}
}
});
})();
<select id="myList" style="width:6em"></select>
This gets you most of the way there, but still has the annoying problem that #hopkins-matt alluded to; namely that if the user opens the drop-down list and either selects the already selected item or moves off of the list without selecting anything, the selection will retain the long version of the text.
The other downside to this approach is that you need to specify the select element's width to keep it from expanding to the maximum length of its hidden option elements.
It's a timing issue.
Change:
<select id="myList" onchange="onChanged()" onclick="onClicked()">
to:
<select id="myList" onchange="onChanged()" onmousedown="onClicked()">
If the user opens the list and moves off the list without clicking, the list will not revert to original unless you call onChanged() on onmouseout as well.
<select id="myList" onchange="onChanged()" onmousedown="onClicked()" onmouseout="onChanged()">
Update: To achieve the best cross browser (onfocus is required for FF, but breaks IE) support without browser sniffing us this combination:
<select id="myList" onchange="onChanged()" onblur="onChanged()" onfocus="onClicked()" onmousedown="onClicked()">
This will also correct the second selection of the same event, but only after the user clicks away from the element.
Update:
Solved... I think.
I rewrote the function you were using to change the options. IE was not firing onchange due to you removing all the option elements and adding new option elements. Which was causing IE to not be able to reference if the user had changed the selection index. The function now just modifies the value and innerHTML of the current option elements. I am using browser sniffing to eliminate the onmouseout call for FF. FireFox was calling onmouseout if you moved the cursor to the dropdown menu. This does cause a side effect in FF. If the user selects the same option in FF, the options do not return to the original state until the onblur is fired.
Demo: http://jsfiddle.net/hopkins_matt/3m1syk6c/
JS:
function changeOptions() {
var selectedIndex = -1;
var click = 0;
var myList = document.getElementById("myList");
var optionArray = ["One|1", "Two|2", "Three|3"];
var fireFox = /Firefox/i.test(navigator.userAgent);
console.log(fireFox);
if (fireFox == false) {
myList.onmouseout=function(){changeList(false)};
}
myList.onblur=function(){changeList(false)};
myList.onchange=function(){changeList(false)};
myList.onmousedown=function(){changeList(true)};
function changeList(listOpen) {
var isListOpen = listOpen;
if (isListOpen == true) {
for (i = 0; i < myList.options.length; i++) {
var pair = optionArray[i].split("|");
myList.options[i].value = pair[0];
myList.options[i].innerHTML = pair[0];
}
}
if (isListOpen == false) {
for (i = 0; i < myList.options.length; i++) {
var pair = optionArray[i].split("|");
myList.options[i].value = pair[1];
myList.options[i].innerHTML = pair[1];
}
}
}
}
changeOptions();
HTML:
<select id="myList">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
Related
I'd like to add the same options elements to more than one select, using one JavaScript function.
<select id="select1" name="select1"></select>
<select id="select2" name="select2"></select>
I want selects become:
<select id="select1" name="select1">
<option value="0">Txt1</option>
<option value="1">Txt2</option>
<option value="2">Txt3</option>
</select>
<select id="select2" name="select2">
<option value="0">Txt1</option>
<option value="1">Txt2</option>
<option value="2">Txt3</option>
</select>
Here is part of function to fill selects with options:
function window_onload(){
var SpecTxt = new Array("Txt1","Txt2","Txt3");
for(var i=0; i<SpecTxt.length; i++) {
var oOption = document.createElement("OPTION");
oOption.text = SpecTxt[i];
oOption.value=i;
select1.add(oOption); // Option to first SELECT
select2.add(oOption); // Option to second SELECT
}
}
But I've got Internet Explorer Script Error "Invalid argument", result is only one first option in "select1" and no options in "select2". If I remove from function window_onload() the last string select2.add(oOption);, there are no IE errors and "select1" is filled as must be, but "select2" is empty. How is it possible in JS to add the same options to different SELECTs?
Update
The reason why the Demo didn't work for IE is because it doesn't recognize the property .valueAsNumber.
From:
var opts = qty.valueAsNumber;
To:
var opts = parseInt(qty.value, 10);
When you create an option within the loop:
var oOption = document.createElement("OPTION");
That is only one <option> not two <option>s. So that is the reason why:
select1.add(oOption); // Succeeds
select2.add(oOption); // Fails
You can either make 2 <option>s per loop:
var oOption1 = document.createElement("OPTION");
var oOption2 = document.createElement("OPTION");
OR try cloneNode(). See Demo below:
Demo
// See HTMLFormControlsCollection
var form = document.forms.ui;
var ui = form.elements;
var qty = ui.qty0;
var s0 = ui.sel0;
var s1 = ui.sel1;
// Declare a counter variable outside of loop
var cnt = 0;
// Add event handler to the change event of the input
qty.onchange = addOpt;
/* Get the value of user input as a number
|| within the for loop...
|| create an <option> tag...
|| add text to it with an incremented offset...
|| add a incremented value to it...
|| then clone it...
|| add original <option> to the first <select>...
|| add duplicate <option> to the second <select>
*/
function addOpt(e) {
var opts = parseInt(qty.value, 10);
for (let i = 0; i < opts; i++) {
var opt = document.createElement('option');
opt.text = 'Txt' + (cnt + 1);
opt.value = cnt;
var dupe = opt.cloneNode(true);
s0.add(opt);
s1.add(dupe);
cnt++;
}
}
input,
select,
option {
font: inherit
}
input {
width: 4ch;
}
<form id='ui'>
<fieldset>
<legend>Enter a number in the first form field</legend>
<input id='qty0' name='qty0' type='number' min='0' max='30'>
<select id="sel0" name="sel0"></select>
<select id="sel1" name="sel1"></select>
</fieldset>
</form>
Reference
HTMLFormControlsCollection
I have 3 columns of HTML selection fields which need to load otions dependent on the previous selection fields.
Selection in column 2 values will be dependant on selected value in column 1 selection. I have this raw JavaScript below which add 2 selection options to a an HTML select filed and then removes 2 based on the select value in selection 1.
My problem is that the part that removes the 2 selection field options is not removing both options but instead removes 1 of them.
Demo: http://jsfiddle.net/jasondavis/croh2nj8/3/
I realize some of this uses jQuery but the goal is to use raw JS without jQuery for this part in question....
$(document).ready(function() {
$("#action").change(function() {
var el = $(this) ;
var selectAttributeEl = document.getElementById('action-attribute');
if(el.val() === "form" ) {
var option = document.createElement('option');
option.text = 'Name';
var option2 = document.createElement('option');
option2.text = 'ActionURL';
selectAttributeEl.add(option);
selectAttributeEl.add(option2);
}else if(el.val() === "link" ) {
for (var i=0; i<selectAttributeEl.length; i++){
if (selectAttributeEl.options[i].value == 'Name'){
selectAttributeEl.remove(i);
}else if(selectAttributeEl.options[i].value == 'ActionURL'){
selectAttributeEl.remove(i);
}
}
}
});
});
The problem is in the for loop where you loop through the selectobject.options. When the first if condition is true, you mutate selectobject.options by removing the Name option. On the next iteration of the loop selectobject.options[i] now returns undefined.
Let's walk through the for loop to demonstrate:
i is 0, corresponding to option ID, nothing happens.
i is 1, corresponding to option Class, nothing happens.
i is 2, corresponding to option Name, the if statement is valid and it removes the Name option. Now selectobject.options has length of 3.
i is 3, which corresponds to undefined. That is, selectobject.options[3] is undefined since the previous iteration of the loop removed an item from selectobject.options.
One possible solution, in the if and else statements you could reset i back one, with i--. Here's an updated jsFiddle. Another option is too loop through selectobject.options backwards, as mutating the latter items won't effect the counter as it moves to the former items.
There are other ways to correct this as well, like creating a new options array based on the values you want to keep in the options, then loading it the new options into the select.
As I stated, you're getting the issue, very simply, because the for loop is started from index 0 and working your way up. When you remove an element, you remove it from the NodeList of options. Easiest way I know of is to start from the end of the node list and work your way to node number 0.
$(document).ready(function() {
$("#action").change(function() {
var el = $(this);
if (el.val() === "form") {
//$("#action-attribute").append(' <option value="Name">Name</option>');
//$("#action-attribute").append(' <option value="ActionURL">ActionURL</option>');
var x = document.getElementById('action-attribute');
var option = document.createElement('option');
option.text = 'Name';
var option2 = document.createElement('option');
option2.text = 'ActionURL';
x.add(option);
x.add(option2);
} else if (el.val() === "link") {
//$("#action-attribute option:last-child").remove() ;
var selectobject = document.getElementById("action-attribute");
var remove_array = ['Name', 'ActionURL'];
for (var i = selectobject.options.length - 1; i >= 0; i--) {
if (remove_array.indexOf(selectobject.options[i].value) != -1) {
selectobject.removeChild(selectobject.options[i]);
}
}
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
When :
<select id="action" name="action">
<option value="link">Link Clicked</option>
<option value="form">Form Submitted</option>
</select>
with:
<select id="action-attribute" name="action-attribute">
<option>ID</option>
<option>Class</option>
</select>
May I propose a different approach? Instead of maintaining the state of the menu by removing elements that shouldn't be there, blow away the menu option tags entirely and replace.
$(document).ready(function() {
var options = {
link: ['ID', 'Class']
},
dependentMenu = document.getElementById('action-attribute');
options.form = options.link.concat(['Name', 'ActionURL']);
$("#action").change(function() {
var el = $(this);
while (dependentMenu.firstChild) {
dependentMenu.removeChild(dependentMenu.firstChild);
}
options[el.val()].forEach(function(value) {
var option = document.createElement('option');
option.text = value;
dependentMenu.add(option);
});
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
When :
<select id="action" name="action">
<option value="link">Link Clicked</option>
<option value="form">Form Submitted</option>
</select>
with:
<select id="action-attribute" name="action-attribute">
<option>ID</option>
<option>Class</option>
</select>
Complete JS novice. I want a "Request A Quote" button to auto-populate a dropdown menu on a new page based on the product and url. Each product quote button links to the same form but with a different hash value in the url which matches an option in the dropdown menu.
Example:
User clicks "Request A Quote" for 'Product A'
User is sent to www.example.com/request-a-quote/#Product A
Product dropdown menu (id=product-select) on form already reads "Product A"
This code works on Chrome, but not for anything else. What am I doing wrong?
//Get select object
var objSelect = document.getElementById("product-select");
var val = window.location.hash.substr(1);
//Set selected
setSelectedValue(objSelect, val)
function setSelectedValue(selectObj, valueToSet) {
for (var i = 0; i < selectObj.options.length; i++) {
if (selectObj.options[i].text== valueToSet) {
selectObj.options[i].selected = true;
return;
}
}
}
I found that applying decodeURIComponent() cleaned up my val variable.
Also, building links as www.example.com/request-a-quote/#Product A is important. If the forward slash is not before the hash, mobile Safari will ignore everything after the hash and it won't work.
Below is my final solution:
//Get select object
var objSelect = document.getElementById("product-select");
var val = decodeURIComponent(window.location.hash.substr(1));
//Set selected
setSelectedValue(objSelect, val)
function setSelectedValue(selectObj, valueToSet) {
for (var i = 0; i < selectObj.options.length; i++) {
if (selectObj.options[i].text== valueToSet) {
selectObj.options[i].selected = true;
return;
}
}
}
Without seeing more code.... The option tag officially supports the value attribute vs text which is the user readable name. We use value as an identifier:
selectObj.options[i].value == valueToSelect;
You will also need to change the select.options markup to use the value attribute rather then text.
UPDATE more info as requested:
The purpose of text is to provide a user readable option. We use value to identify the selection to the server and in your case the URL hash. By using the value attribute, you can use URL safe values and user readable text.
The fix you posted in your answer is really bad practice and will become problematic as the complexity of your code increases.
This example will work in all browsers and is the proper way to implement.
//Simulate hash
window.location.hash = '2'
var val = window.location.hash.substr(1);
var selectEle = document.getElementById('select')
setSelectedValue(selectEle, val)
function setSelectedValue(selectObj, valueToSet) {
for (var i = 0; i < selectObj.options.length; i++) {
var selection = selectObj.options[i]
if (selection.value == valueToSet) {
selection.selected = true;
}
}
}
<select name="selections" id="select">
<option value="1">Product A</option>
<option value="2">Product B</option>
<option value="3">Product C</option>
</select>
function keyy(id)
{
var value;
var selected;
var select = document.getElementById(id);
if(value != null)
select.options[selected].text = value;
selected = select.selectedIndex;
var key;
key =select.options[selected].value;
value= select.options[selected].text;
select.options[selected].innerHTML = key;
}
<select id="Carss" name="Cars" onchange="keyy(this.id)" >
<option value="A">Audi</option>
<option value="M">Mercedes</option>
</select>
I have n dropdown values. When I select one value, the corresponding key should be displayed. The drop down should be the values and the display item shoud be the coresponding key.
Have atached the image for the reference.
My code :
var value;
var selected;
function keyy(id) {
var select = document.getElementById(id);
if(value != null)
select.options[selected].text = value;
selected = select.selectedIndex;
var key;
key =select.options[selected].value;
value= select.options[selected].text;
select.options[selected].text = key;
}
What you're trying to do is impossible (with a native <select>). The item you see in the closed <select> is simply the <option> that is currently selected. When you open the drop-down, you see the same <option> in two places - in the "selection" and in the "list". You cannot see a different value in each of the places, when it's the same <option>.
You could, however, show the selected value somewhere else, e.g. in a second element next to the <select>.
This is not a perfect answer..
But a possible work around..
function key() {
document.getElementById("Carss").style.width = "100px"
}
function key2() {
document.getElementById("Carss").style.width = "34px"
document.getElementById("Carss").blur();
}
<select id="Carss" name="Cars" onfocus="key()" onchange="key2()" style="width:34px">
<option value="A">Audi</option>
<option value="M">Mercedes</option>
</select>
I have created a javascript search in select element.
option tag does not get any CSS to hide or display none, for this solution I have removed unmatched option and make a backup for removed option for reset list button.
It's working fine but I have a problem, I have about 19000 option for this select list.
search works fine but when I hit reset button, only 9500 option from 19000 comes back.
I appreciate your help.
Here is the code:
CodePen Demo
HTML
<h1>Search in select "option" tag</h1>
<select multiple name="selectMenu" id="selectMenu" style="width:100px" size=10>
<option value ="item 1">item 1</option>
<option value ="item 2">item 2</option>
<option value ="thing 3">thing 3</option>
<option value ="item 4">item 4</option>
<option value ="stuff 5">stuff 5</option>
<option value ="stuff 6">stuff 6</option>
<option value ="stuff 7">stuff 7</option>
<option value ="item 8">item 8</option>
</select>
<p>Search within this list</p>
<input type=text name="search" id="search" onkeypress="searchItems();">
<br>
<input type=button value="Search" onclick="searchItems();">
<input type=button value="Reset List" onclick="resetList();">
Javascript
var itemList = null;
var itemListOriginal = new Array();
var backup = false;
function searchItems() {
itemList = document.getElementById("selectMenu");
var searchStrObj = document.getElementById("search");
var itemDescription = "";
// replace white space with wild card
var searchString = searchStrObj.value;
searchString = searchString.replace(" ", ".*");
var re = new RegExp("(" + searchString + ")", "i"); //"i" sets "ignore case" flag
if (itemListOriginal.length < 1)
backup = true;
else
backup = false;
// loop through options and check for matches
for (i=itemList.options.length - 1; i >=0 ; i--) {
itemDescription = itemList.options.item(i).text;
if (backup) {
hash = new Array();
hash['name'] = itemDescription;
hash['value'] = itemList.options.item(i).value;
itemListOriginal[i] = hash;
}
if (!itemDescription.match(re)) {
itemList.remove(i);
}
}
return false;
}
function resetList() {
//hack! remove all elements from list before repopulating
for (i=itemList.options.length - 1; i >=0 ; i--) {
itemList.remove(i);
}
for (i=0; i < itemListOriginal.length; i++) {
hash = itemListOriginal[i];
//option = new Option(hash['name'], hash['value']); REMOVED
//itemList.options.add(option, i); REMOVED
itemList.options[i] = new Option(hash['name'], hash['value'], false, false);
}
document.getElementById("search").value = "";
}
DEMO
I've observed that in your code backup is changed every time you call the function searchitems().
Thus erasing the old values that were stored in it.
So I've changed that
It is working fine but I have a problem,
I have about 19000 option for this select list.
search works fine but when I hit reset button,
only 9500 option from 19000 comes back.
That is the reason behind that. So I've modified your code and added a global variable backupList in that.
so when the unwanted elements are removed old elements aren't deleted in my code but instead, new removed elements are appended to old removed elements using += shorthand operator.
also rather creating options dynamically and using .add or .append or any similar javascript method I'm using .innerHTML for simplicity as you can see in the code. only problem is that now after you click reset elements will not be sorted as it was in the first case, You'll need to sort them believe me it is easy. for sorting refer: sort select menu alphabetically?.
var itemList = null;
var itemListOriginal = new Array();
var backup = false;
var backupList =""; // To store removed elements
function searchItems() {
itemList = document.getElementById("selectMenu");
var searchStrObj = document.getElementById("search");
var itemDescription = "";
var searchString = searchStrObj.value;
searchString = searchString.replace(" ", ".*");
var re = new RegExp("(" + searchString + ")", "i"); //"i" sets "ignore case" flag
for (i=itemList.options.length - 1; i >=0 ; i--) {
itemDescription = itemList.options.item(i).text;
var hash = new Array();
hash['name'] = itemDescription;
hash['value'] = itemList.options.item(i).value;
itemListOriginal[i] = hash;
if (!itemDescription.match(re)) {
itemList.remove(i); //Remove Unwanted Elements
backupList+="<option value='"+ hash['value']+"'>"+itemDescription+"</option>";
/* append new unwanted elements with previous,
initially it is blank "".
This is Important
*/
}
}
return false;
}
function resetList() {
var itemList = document.getElementById("selectMenu");
itemList.innerHTML+=backupList; /* Add removed elements to list.
alternate to .append,.add or similar function*/
backupList=""; // Make Backup Empty!
document.getElementById("search").value = "";
}
Hope it helps! cheers :)!