My JavaScript skills are pretty basic, but I've written this code where you select from the dropdown on the left (Select1) an option which shows only the specified options in the dropdown on the right (Select2), and hides the rest.
I've also put it in a codepen here, in case you want to fiddle.
The code seems to work in both of the above environments in Firefox 90.0.2, but it fails in both and writes nothing to the console in Chrome 92.0.4515.131.
Any ideas why it's working in Firefox but not Chrome (and others) and what I can do so it works on all major browsers.
I'm running Windows 10 and I'd like to avoid iQuery if practical, as I don't want to get into learning or using that yet, as I'm starting with the basics.
Thanks.
Here's the code:
<!DOCTYPE html>
<html>
<head>
<script>
function hide_options(select_id,type)
{
//alert("hide_options(select_id="+select_id+", type="+type+")");
console.log("hide_options(select_id="+select_id+", type="+type+")");
var x = document.getElementById(select_id);
for (i=1; i<x.options.length; i++)
{
x.options[i].style.display = "none";
}
x.options[0].selected = true;
if (type == 'A')
{ unhide_options(select_id,"one","two") }
if (type == 'B')
{ unhide_options(select_id,"two","three") }
if (type == 'C')
{ unhide_options(select_id,"two") }
}
function unhide_options(select_id,...opts)
{
//alert("unhide_options(select_id="+select_id+"opts="+opts+")");
console.log("unhide_options(select_id="+select_id+"opts="+opts+")");
for (i=0; i<opts.length; i++)
{
document.getElementById(select_id+"_"+opts[i]).style.display = "";
}
}
</script>
</head>
<body>
<p>Selecting an option in the "Select1" dropdown should show only those options in the "Select2" dropdown.</p>
<select name=select1>
<option>Select1...</option>
<option onclick="hide_options('field1','A')">Show options 1 + 2 only</option>
<option onclick="hide_options('field1','B')">Show options 2 + 3 only</option>
<option onclick="hide_options('field1','C')">Show option 2 only</option>
</select>
<select name=update_action id=field1>
<option value=''>Select2...</option>
<option value=one id=field1_one>One</option>
<option value=two id=field1_two>Two</option>
<option value=three id=field1_three>Three</option>
</select>
</body>
</html>
Perhaps Chrome doesn't emit an event on clicking an option - however, you can use the change event on the select
I've used data-* attributes for the options, and I also show/hide in the one loop
the showing object is just a nice easy way to configure what should show, so you don't need if/else if/else if etc - I find this easier to maintain then countless if else if`'s
const selects = document.querySelectorAll('.filterer');
const showing = {
A: ["one", "two"],
B: ["two", "three"],
C: ["two"]
};
selects.forEach(select => {
select.addEventListener('change', function(e) {
const {
target,
value
} = this[this.selectedIndex].dataset;
const show = showing[value] || [];
const x = document.getElementById(target) || [];
[...x]
.slice(1)
.forEach(option =>
option.style.display = (show.length === 0 || show.includes(option.value)) ? '' : 'none'
);
if (x && x[0]) {
x[0].selected = true;
}
});
});
<p>Selecting an option in the "Select1" dropdown should show only those options in the "Select2" dropdown.</p>
<select id="select1" name="select1" class="filterer">
<option>Select1...</option>
<option data-target='field1' data-value='A'>Show options 1 + 2 only</option>
<option data-target='field1' data-value='B'>Show options 2 + 3 only</option>
<option data-target='field1' data-value='C'>Show option 2 only</option>
</select>
<select name=update_action id=field1>
<option value=''>Select2...</option>
<option value=one id=field1_one>One</option>
<option value=two id=field1_two>Two</option>
<option value=three id=field1_three>Three</option>
</select>
<br/>
<hr/>
<select id="select2" name="select2" class="filterer">
<option>Select1...</option>
<option data-target='field2' data-value='A'>Show options 1 + 2 only</option>
<option data-target='field2' data-value='B'>Show options 2 + 3 only</option>
<option data-target='field2' data-value='C'>Show option 2 only</option>
</select>
<select name=update_action id=field2>
<option value=''>Select2...</option>
<option value=one id=field2_one>One</option>
<option value=two id=field2_two>Two</option>
<option value=three id=field2_three>Three</option>
</select>
Alternative. You mention in a comment that the Selects and Options are created dynamically on the server code
The following would allow you to do that, and to specify the visible selections given the current choice, all in the option data-values attribute
Nothing needs hard coding in javascript this way
const selects = document.querySelectorAll('.filterer');
selects.forEach(select => {
select.addEventListener('change', function(e) {
const { target, values } = this[this.selectedIndex].dataset;
const show = values.split(',').map(s => s.trim());
const x = document.getElementById(target) || [];
[...x]
.slice(1)
.forEach(option =>
option.style.display = (show.length === 0 || show.includes(option.value)) ? '' : 'none'
);
if (x && x[0]) {
x[0].selected = true;
}
});
});
<p>Selecting an option in the "Select1" dropdown should show only those options in the "Select2" dropdown.</p>
<select id="select1" name="select1" class="filterer">
<option>Select1...</option>
<option data-target='field1' data-values='one, two'>Show options 1 + 2 only</option>
<option data-target='field1' data-values='two, three'>Show options 2 + 3 only</option>
<option data-target='field1' data-values='two'>Show option 2 only</option>
</select>
<select name='update_action' id='field1'>
<option value=''>Select2...</option>
<option value='one' id='field1_one'>One</option>
<option value='two' id='field1_two'>Two</option>
<option value='three' id='field1_three'>Three</option>
</select>
<br/>
<hr/>
<br/>
<select id="select2" name="select2" class="filterer">
<option>Select1...</option>
<option data-target='field2' data-values='apple,banana'>Apple and banana</option>
<option data-target='field2' data-values='banana,pineapple'>Banana and pineapple</option>
<option data-target='field2' data-values='pineapple'>Pineapple</option>
</select>
<select name='update_action' id='field2'>
<option value=''>Select2...</option>
<option value='pineapple' id='field2_one'>Pineapple</option>
<option value='banana' id='field2_two'>Banana</option>
<option value='apple' id='field2_three'>Apple</option>
</select>
Sure, this may require changing server code, but your original code did have hard coded logic for A B and C - this code, the server code can emit anything it likes and the javascript doesn't need to be changed
Related
I'm trying to show/hide the cart button depending on 2 separate select field values.
My logic is this:
Display the cart button if selectId #pa_custom-engraving = 'no' OR if selectId #pa-color != 'custom-print'. Otherwise I want to hide the cart.
This is what I have so far which works unless you continue to toggle the select fields they cancel each other out. How can I combine this into proper 'OR' conditional statement?
JS
document.getElementById('pa_custom-engraving').addEventListener('change', function () {
var style = this.value == 'no' ? 'block' : 'none';
document.getElementsByClassName('woocommerce-variation-add-to-cart')[0].style.display = style;
});
document.getElementById('pa_color').addEventListener('change', function () {
var pstyle = this.value !== 'custom-print' ? 'block' : 'none';
document.getElementsByClassName('woocommerce-variation-add-to-cart')[0].style.display = pstyle;
});
HTML
<select id="pa_custom-engraving">
<option value="">Choose an option</option>
<option value="no">No</option>
<option value="yes">Yes</option>
</select>
<select id="pa_color">
<option value="">Choose an option</option>
<option value="black">Black</option>
<option value="white">White</option>
<option value="custom-print">Custom Print</option>
</select>
Add the same listener to both selects. On change, look at both select values, and apply the conditions separated by ||:
const cartButton = document.querySelector('.woocommerce-variation-add-to-cart');
const selects = ['#pa_custom-engraving', '#pa_color'].map(c => document.querySelector(c));
for (const select of selects) {
select.addEventListener('change', () => {
const displayStyle= selects[0].value === 'no' || selects[1].value !== 'custom-print'
? 'block'
: 'none';
cartButton.style.display = displayStyle;
});
}
You can take the all selected value in an array and check if the array includes/not include the value you want to match:
var sel = Array.from(document.querySelectorAll('#pa_custom-engraving, #pa_color'));
sel.forEach(function(el){
el.addEventListener('change', function () {
var val = sel.map(i => i.value);
var style = val.includes('no') || !val.includes('custom-print') ? 'block' : 'none';
document.getElementsByClassName('woocommerce-variation-add-to-cart')[0].style.display = style;
});
});
<select id="pa_custom-engraving">
<option value="">Choose an option</option>
<option value="no">No</option>
<option value="yes">Yes</option>
</select>
<select id="pa_color">
<option value="">Choose an option</option>
<option value="black">Black</option>
<option value="white">White</option>
<option value="custom-print">Custom Print</option>
</select>
<button class="woocommerce-variation-add-to-cart">woocommerce-variation-add-to-cart</button>
I want to hide the option in the selection box if the option is already selected in another box. I am not sure where the problem is in my code, I tried running it on different editors it didn't work. Here is my code:
<html>
<head>
<title>Currency Converter</title>
</head>
<body>
<script langauge="javascript">
function CheckDropDowns() {
ListOfSelectedCountires = [];
for (CountryNumber = 0; CountryNumber < 3; CountryNumber++) {
ListOfSelectedCountires[CountryNumber] = document.getElementById("country" + (CountryNumber + 1)).value;
}
for (algoritmCountryNumber = 0; algoritmCountryNumber < 3; algoritmCountryNumber++) {
for (countryOptions = 1; countryOptions < 5; countryOptions++) {
document.getElementById("country" + (algoritmCountryNumber + 1)).options[countryOptions].style.display = "block";
for (processedOption = 0; processedOption < 3; processedOption++) {
if (document.getElementById("country" + (algoritmCountryNumber + 1).options[countryOptions].value == ListOfSelectedCountires[processedOption]) {
document.getElementById("country" + (algoritmCountryNumber + 1)).options[countryOptions].style.display = "none";
}
}
}
}
}
</script>
<section>
<select id="country1" onchange="CheckDropDowns()">
<option value="">Choose</option>
<option value="1">Australia</option>
<option value="2">Indonesian Rupiah</option>
<option value="3">Chinese Yaun</option>
<option value="4">Japanese Yen</option>
</select>
Convert to
<select id="country2" onchange="CheckDropDowns()">
<option value="">Choose</option>
<option value="1">Australia</option>
<option value="2">Indonesian Rupiah</option>
<option value="3">Chinese Yaun</option>
<option value="4">Japanese Yen</option>
</select>
</section>
</body>
</html>
First determine which select needs to be filtered, then loop through the options setting display to block first(to undo the previous filter), and then if the option's value matches the selected value set it's display to none.
Edit
Above method does not work in every browser. The better HTML5 way is to set and remove the hidden attribute. I've updated the snippet.
window.CheckDropDowns = function(thisSelect) {
var otherSelectId = ("country1" == thisSelect.id) ? "country2" : "country1";
var otherSelect = document.getElementById(otherSelectId);
for (i = 0; i < otherSelect.options.length; i++) {
//otherSelect.options[i].style.display = 'block';
otherSelect.options[i].removeAttribute('hidden');
if (otherSelect.options[i].value == thisSelect.value) {
//otherSelect.options[i].style.display = 'none';
otherSelect.options[i].setAttribute('hidden', 'hidden');
}
}
}
<section>
<select id="country1" onchange="CheckDropDowns(this)">
<option value="">Choose</option>
<option value="1">Australia</option>
<option value="2">Indonesian Rupiah</option>
<option value="3">Chinese Yaun</option>
<option value="4">Japanese Yen</option>
</select>
Convert to
<select id="country2" onchange="CheckDropDowns(this)">
<option value="">Choose</option>
<option value="1">Australia</option>
<option value="2">Indonesian Rupiah</option>
<option value="3">Chinese Yaun</option>
<option value="4">Japanese Yen</option>
</select>
</section>
I don't know what's wrong with your code, the logic seems convoluted. You can't reliably hide options by setting their display to 'none', in some browsers they remain visible. If it was that simple, life would be easy.
You can hide options by removing them from the list, but then you have to remember where they came from so you can put them back when the selection changes.
In the following, each time an option is selected, if there's one stored it's put back and the one matching the selected option is removed. If the first option is selected, the stored one is just put back, nothing is removed.
This only depends on the two selects having the same class value, they could be related by some other value (e.g. a data-* property).
Hopefully the comments are sufficient.
var matchSelected = (function() {
// Store for "hidden" node
var nodeStore = {
sourceElement: null,
node: document.createDocumentFragment(),
index: null
};
return function(evt) {
// Get the element that got the event
var tgt = this;
// If there's a stored option, put it back
if (nodeStore.sourceElement) {
let sel = nodeStore.sourceElement;
sel.insertBefore(nodeStore.node.firstChild, sel.options[nodeStore.index]);
nodeStore.sourceElement = null;
nodeStore.node = document.createDocumentFragment();
nodeStore.index = null;
}
// If the selected option is the first one, stop here
if (tgt.selectedIndex == 0) return;
// Get all selects with the same className
var sels = document.querySelectorAll('.' + this.className);
// Get the "other" option
var other = sels[0] == this ? sels[1] : sels[0];
// Remove and keep the option on the other element that is the same
// as the selected option on the target element
nodeStore.sourceElement = other;
nodeStore.index = tgt.selectedIndex;
nodeStore.node.appendChild(other.options[tgt.selectedIndex]);
}
}());
window.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.countrySelect').forEach(
node => node.addEventListener('change', matchSelected, false)
);
}, false);
<select id="country1" class="countrySelect">
<option value="" selected>Choose</option>
<option value="1">Australia Dollar</option>
<option value="2">Indonesian Rupiah</option>
<option value="3">Chinese Yaun</option>
<option value="4">Japanese Yen</option>
</select>
Convert to
<select id="country2" class="countrySelect">
<option value="" selected>Choose</option>
<option value="1">Australia Dollar</option>
<option value="2">Indonesian Rupiah</option>
<option value="3">Chinese Yaun</option>
<option value="4">Japanese Yen</option>
</select>
I dynamically generate additional forms on a page using Django Model Formsets. The user can generate as many forms as they need. This is done in a Vuejs method.
My issue is changing the options of the second select of the form that the first select value was chosen. I was thinking about trying on focus or on click to get the select that was last changed, but I'm not sure how that would work.
I do track the current count of forms on the page.
In the example below. If the user changed form_select_0 to One, I need form_subselect_0 to only have options C and D, but the selects in form_1 should not be altered.
Example:
<form id = form_0>
<select id="form_select_0">
<option value="one">One</option>
<option value="two">Two</option>
</select>
<select id="form_subselect_0">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
</select>
</form>
<form id = form_0>
<select id="form_select_1">
<option value="one">One</option>
<option value="two">Two</option>
</select>
<select id="form_subselect_1">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
</select>
</form>
Follow-up:
Added vue method (below) for how I'm currently adding Vuejs models to the Django Model Formsets. Question: Is there a better way?
addForm: function () {
this.count++
let form_count = this.count
form_count++
let formID = 'id_form-' + this.count
incremented_form = this.vue_form.replace(/form-\d/g, 'form-' + this.count)
this.formList.push(incremented_form)
this.$nextTick(() => {
let total_forms = document.getElementsByName('form-TOTAL_FORMS').forEach
(function (ele, idx) {
ele.value = form_count
})
})
},
As stated above this was my final answer due to no response:
addForm: function () {
this.count++
let form_count = this.count
form_count++
let formID = 'id_form-' + this.count
incremented_form = this.vue_form.replace(/form-\d/g, 'form-' + this.count)
this.formList.push(incremented_form)
this.$nextTick(() => {
let total_forms = document.getElementsByName('form-TOTAL_FORMS').forEach
(function (ele, idx) {
ele.value = form_count
})
})},
I am trying to come up with a way that would automatically populate a drop down based on the value selected in a different drop down.
Lets say I have a drop down for State / Province. Depending which value gets selected it would fill in the Country drop down to be USA / CAN.
<select name="formState" class="form-control modeldropdown" id="state" required>
<option value="">State / Province</option>
<option value="">--- United States ---</option>
<option value="AL">Alabama - AL</option>
...
<option value="MH">Marshall Islands - MH</option>
<option value="">--- Canada ---</option>
<option value="AB">Alberta - AB</option>
<option value="BC">British Columbia - BC</option>
...
</select>
It would be easier to do it the reversed way (select a country then it filters) -- for that I would do something like this
var jsonText = '{"USA":{ '
+ '"Alabama": "Alabama",'
+ '"Alaska": "Alaska",'
+ '},'
+ '"CAN":{'
+ '"Alberta": "Alberta",'
+ '}}';
var json = JSON.parse(jsonText);
And then filter it based on which Country was selected, but is there an easy way to do the reversed?
The only way I can think to do this is by checking the value to see if it is:
var state = $('#state').val();
if ( state == 'Alabama' || state == 'Alaska'...)
...
You could make an object that maps from state/province to country:
const countryOfState = {
'Alabama' : 'USA',
'Alberta' : 'CAN'
};
let state = $('#state').val();
if (countryOfState[state] === 'USA') {
...
You can use indexOf to check if an element exists in an array like so:
const states = {
'USA': ['Alabama'],
'CAN': ['Alberta']
};
function getCountry(state) {
for (let country in states) {
if (states[country].indexOf(state) >= 0) {
return country;
}
}
return null;
}
console.log(getCountry('Alberta')); //CAN
Here's a slightly sideways approach using optgroup which I think works better for your data anyway. I've also utilised data attributes
$('#state').change(function() {
var optGroup = $(this.options[this.selectedIndex]).closest('optgroup');
var label = $(optGroup).prop('label');
var cCode = $(optGroup).data('countrycode');
$('#country').html('<option value="'+ cCode + '">' + label + '</option>');
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<select name="formState" class="form-control modeldropdown" id="state" required>
<option value="">State / Province</option>
<optgroup label="United States" data-countryCode="USA">
<option value="AL">Alabama - AL</option>
<!-- ... -->
<option value="MH">Marshall Islands - MH</option>
</optgroup>
<optgroup label="Canada" data-countryCode="CA">
<option value="AB">Alberta - AB</option>
<option value="BC">British Columbia - BC</option>
</optgroup>
</select>
<select id="country" name="country"><option disabled selected>Please Select State</option></select>
1) I have two drop downs with exactly the same values. I want the drop down 2 to display the values based on the selection of items of drop down 1. So the selected index of drop down 2 will be equal to or more than the selected index of drop down 1. ( this code is working)
but When I add one more drop down and based on its items the other two dropdowns should behave as:
2) If I select TCD in the first Dropdown and change to value B in the second dropdown the value should be B in the third dropdown too but If I select BCD from the first dropdown it should retain the values of other two dropdown from the previous selection.( should not go back to A)
The first part is working but the second part is having issues.
Fiddle : 1) http://jsfiddle.net/wtLm4805/2/
Fiddle with three dropdowns : 2) http://jsfiddle.net/wtLm4805/3/
while (select2.firstChild) {
select2.removeChild(select2.firstChild);
}
for (var i = 0; i < select1.options.length; i++) {
var o = document.createElement("option");
o.value = select1.options[i].value;
o.text = select1.options[i].text;
(i < select1.selectedIndex)
? o.disabled = true
: o.disabled = false ;
select2.appendChild(o);
}
Where am I going wrong ?
You can go somewhere along these lines
var typeValue = 'TCD'; // default initialisation
$('#Type').change(function(){
var value = $(this).val();
console.log(value);
if(value == 'TCD')
{
typeValue = 'TCD';
// change something in other selects too
}
else if(value == 'MCD')
{
typeValue = 'MCD';
}
else if(value == 'BCD')
{
$('#SELECTA').val('B');
$('#SELECTB').val('B');
typeValue = 'BCD';
}
});
$('#SELECTA').change(function(){
var value = $(this).val();
console.log(value);
if(typeValue = 'TCD')
{
$('#SELECTB').val(value);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<select name="Type" id="Type" >
<option value="TCD">TCD</option>
<option value="MCD" >MCD</option>
<option value="BCD" >BCD</option>
</select>
<select id="SELECTA" class="SELECTA">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
</select>
<select id="SELECTB" class="SELECTB" >
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
</select>
Since you have only one element with class SELECTA and one with class SELECTB, these will always be undefined:
var select1 = document.getElementsByClassName("SELECTA")[1];
var select2 = document.getElementsByClassName("SELECTB")[1];
var select1 = document.getElementsByClassName("SELECTA")[2];
var select2 = document.getElementsByClassName("SELECTB")[2];
If you're trying to target the options, you could move the classes to the options themselves, or you could reference them like this:
document.getElementsByClassName("SELECTA")[0].options[1]
Not sure why you're deleting/adding items to the SELECTB element, but is this what you're going for?
function clickButton() {
var Type= document.getElementById('Type');
var select1= document.getElementById('SELECTA');
var select2= document.getElementById('SELECTB');
if(Type.value === 'TCD') {
for(var i = 0 ; i < select1.options.length ; i++) {
select2.options[i].disabled= i < select1.selectedIndex;
}
select2.value= select1.value;
}
else {
for(var i = 0 ; i < select2.options.length ; i++) {
select2.options[i].disabled= false;
}
}
}
<select name="Type" id="Type" onchange="clickButton()">
<option value="TCD">TCD</option>
<option value="MCD">MCD</option>
<option value="BCD">BCD</option>
</select>
<select id="SELECTA" onchange="clickButton()">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
</select>
<select id="SELECTB">
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
</select>