Dynamic select

I have two select boxes

<select onchange="showAlignment(this)">
<option value="one">1</option>
<option value="two">2</option>

</select>
<select>
<option id="One">1</option>
<option id="Two">2</option>
<option id="Three">3</option>
<option id="Four">4</option>
</select>

When an option is selected, i want to run a function to remove an option from the 2nd group
Is this right?

function showAlignment(select) {
   if(select.options[select.selectedIndex].getValue("one"))
   {
   document.getElementById("Three").style.display = "none";
	}
}

Hi there lurtnowski,

try it like this…

<!DOCTYPE HTML>
<html lang="en">
<head>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">

<title>Untitled Document</title>

<style media="screen">
.hide {
    display: none;
 }
</style>

</head>
<body><!DOCTYPE HTML>
<html lang="en">
<head>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">

<title>Untitled Document</title>

<style media="screen">
.hide {
    display: none;
 }
</style>

</head>
<body>

 <select>
  <option value="">options one</option>
  <option value="one">1</option>
  <option value="two">2</option>
 </select><select>
  <option value="">options two</option>
  <option value="one">1</option>
  <option value="two">2</option>
  <option value="three">3</option>
  <option value="four">4</option>
 </select>

<script>
( function( d ) {
   'use strict';
   
   var sel = d.querySelectorAll( 'select' );
   
   sel[0].addEventListener( 'change',
      function() {
         if ( this.value === 'one' ) {
              sel[1].options[3].classList.add( 'hide' );
	      }
         else {
              sel[1].options[3].classList.remove( 'hide' );
	      }
      }, false );
}( document ));
</script>

</body>
</html>

coothead

2 Likes

ok, ran into a problem trying to get this on my site
heres the function

<script>
( function( d ) {
   'use strict';
   
   var sel = d.querySelectorAll( 'select' );
   
   sel[6].addEventListener( 'change',
      function() {
		  console.log('Test');
         if ( this.value === 'zero' ) {
              sel[7].options[1].classList.add( 'hide' );
			  console.log("True");
	      }
         else {
              sel[7].options[1].classList.remove( 'hide' );
	      }
      }, false );
}( document ));
</script>

the result


when I make a selection in the width box, test appears in the console.
So the event is fired, but why does the 2nd option stay in the next select box?
shouldn’t left dissapear?
I noticed hide doesn’t appear in the element inspector, but if I manually add display:none, it goes away

Can you please show us what the HTML looks like for the Select and option elements too?

he4res the plugin

my select boxes

<select class="form-control" id="Orientation" required name="Width">
<option value="">Select</option>
<option value="100">100%</option>
<option value="50">50%</option>
<option value="33">33%</option>
<option value="25">25%</option>
</select>	

  <select name="webmenu" id="webmenu">
    <option value="1" data-image="../images/alignment_0.png">&nbsp;&nbsp;None</option>
    <option value="1" data-image="../images/alignment_1.png">&nbsp;&nbsp;Left</option>
    <option value="2" data-image="../images/alignment_2.png">&nbsp;&nbsp;Right</option>
    <option value="1" data-image="../images/alignment_3.png">&nbsp;&nbsp;Left</option>
    <option value="3" data-image="../images/alignment_4.png">&nbsp;&nbsp;Center</option>
    <option value="4" data-image="../images/alignment_5.png">&nbsp;&nbsp;Right</option>
    <option value="1" data-image="../images/alignment_6.png">&nbsp;&nbsp;Left</option>
    <option value="5" data-image="../images/alignment_7.png">&nbsp;&nbsp;Center-Left</option>
    <option value="2" data-image="../images/alignment_8.png">&nbsp;&nbsp;Center-Right</option>
    <option value="6" data-image="../images/alignment_9.png">&nbsp;&nbsp;Right</option>
  </select>	

the script

( function( d ) {
   'use strict';
   
   var sel = d.querySelectorAll( 'select' );
   
   sel[6].addEventListener( 'change',
      function() {
		  console.log('Test');
         if ( this.value === '100' ) {
              sel[7].options[1].classList.add( 'hide' );
			  console.log("True");
	      }
         else {
              sel[7].options[1].classList.remove( 'hide' );
	      }
      }, false );
}( document ));

result, as you can see from the console, the function fires, but the option doessn’t go away.

The selectors you are using look to be far too brittle, capable of breaking if anything about the number of selectors changes.

I have some test code working where the orientation and webmenu are referenced by the unique id instead. Give that a try and see if there’s any improvement there.

(function(d) {
    "use strict";

    function isAHundred(option) {
        return option.value === "100";
    }
    function changeOrientationHandler(evt) {
        var option = evt.target;
        var webmenu = d.querySelector("#webmenu");
        webmenu.classList.toggle("hide", isAHundred(option));
    }
    var orientation = d.querySelector("#Orientation");
    orientation.addEventListener("change", changeOrientationHandler);
}(document));

test code: https://jsfiddle.net/3mg4pzqf/2/

2 Likes

Oopsie, ran into a problem
It was working fine when I was not using the plugin to allow images in the select box

I think that error means that the 2nd select box (the plugin converted into a

<ul>

and thats wht there is no snd select box, heres a screenie of it when I inspect the element


Thou ght it was just a simple

document.getElementsByTagName('li')[2].className += " hide";

to get the 2nd li to dissapear, but I was mistaken


Thanks

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">

  <title>The HTML5 Herald</title>
  <meta name="description" content="The HTML5 Herald">
  <meta name="author" content="SitePoint">
  <style>
.hide {
    display: none;
 }
  .dd-selected-text {line-height:1.4em !important}
  </style>
</head>
<body>
<select id="Width">
<option></option>
<option value="100">100%</option>
<option>50%</option>
<option>33%</option>
<option>25%</option>

</select>
    <select id="myDropdown">
        <option value="0" data-imagesrc="images/Alignment_0.png">None</option>
        <option value="1" data-imagesrc="images/Alignment_1.png">Left</option>
        <option value="2" data-imagesrc="images/Alignment_2.png" >Right</option>
        <option value="3" data-imagesrc="images/Alignment_3.png">Left</option>
        <option value="4" data-imagesrc="images/Alignment_4.png">Center</option>
        <option value="5" data-imagesrc="images/Alignment_5.png">Right</option>
        <option value="6" data-imagesrc="images/Alignment_6.png">Left</option>
        <option value="7" data-imagesrc="images/Alignment_7.png">Center-Left</option>
        <option value="7" data-imagesrc="images/Alignment_8.png">Center-Right</option>
        <option value="8" data-imagesrc="images/Alignment_9.png">Right</option>
    </select>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
 <script type="text/javascript" src="https://cdn.rawgit.com/prashantchaudhary/ddslick/master/jquery.ddslick.min.js" ></script>
 <script>
	$('#myDropdown').ddslick({
});
</script>
<script>
( function( d ) {
   'use strict';
   
   var sel = d.querySelectorAll( 'select' );
   
   sel[0].addEventListener( 'change',
      function() {
         if ( this.value === '100' ) {
              sel[1].options[3].classList.add( 'hide' );
	      }
         else {
              sel[1].options[3].classList.remove( 'hide' );
	      }
      }, false );
}( document ));
</script>
	</body>
	</html>
```,

Yes, that’s exactly the “brittle” that I was mentioning before, and recommended changes to make things less brittle.

oh, I was looking at your example. how do I make it so that the whole select doest get hidden.
If I give each option an id, can I only add the class to those?

so your code is working great (no errors)
But when 100% is selected from the first dropdown, the second select box dissapears. Is there a way to make all the options dissapear except the first (so they can only make 1 selection?)
when 50% is selected I want all but 2 options gone.
33% would result in all but 3 options,
and lastly the 25% would only result in 4 options left
Heres my (slightly modified code

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">

  <title>The HTML5 Herald</title>
  <meta name="description" content="The HTML5 Herald">
  <meta name="author" content="SitePoint">
  <style>
.hide {
    display: none;
 }
  .dd-selected-text {line-height:1.4em !important}
  </style>
</head>
<body>
<select id="Width">
<option></option>
<option value="100">100%</option>
<option>50%</option>
<option>33%</option>
<option>25%</option>

</select>
    <select id="myDropdown">
        <option value="0" data-imagesrc="images/Alignment_0.png">None</option>
        <option value="1" data-imagesrc="images/Alignment_1.png">Left</option>
        <option value="2" data-imagesrc="images/Alignment_2.png" >Right</option>
        <option value="3" data-imagesrc="images/Alignment_3.png">Left</option>
        <option value="4" data-imagesrc="images/Alignment_4.png">Center</option>
        <option value="5" data-imagesrc="images/Alignment_5.png">Right</option>
        <option value="6" data-imagesrc="images/Alignment_6.png">Left</option>
        <option value="7" data-imagesrc="images/Alignment_7.png">Center-Left</option>
        <option value="7" data-imagesrc="images/Alignment_8.png">Center-Right</option>
        <option value="8" data-imagesrc="images/Alignment_9.png">Right</option>
    </select>
    <script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
 <script type="text/javascript" src="https://cdn.rawgit.com/prashantchaudhary/ddslick/master/jquery.ddslick.min.js" ></script>
 <script>
	$('#myDropdown').ddslick({
});
</script>
<script>
(function(d) {
    "use strict";

    function isAHundred(option) {
        return option.value === "100";
    }
    function changeOrientationHandler(evt) {
        var option = evt.target;
        var myDropdown = d.querySelector("#myDropdown");
        myDropdown.classList.toggle("hide", isAHundred(option));
    }
    var orientation = d.querySelector("#Width");
    orientation.addEventListener("change", changeOrientationHandler);
}(document));
</script>
	</body>
	</html>
	

thanks

You’ll need to provide more explicit details about what you want showing when each option is selected.
(I want you to practice your ability to give useful details. There is still much ambiguity.)

sure, sorry for not explaining adequatelly,
so with the first select box (where the options are 100%, 50%, 33%, and 25%) are to select a width.
The 2nd select is to select the alignment.
So If a width of 100% is selected, only 1 alignment (None) would be available.
So If a width of 50% is selected, only 2 alignments (Left, Right) would be available.
So If a width of 33% is selected, only 3 alignments (Left, Center, Right) would be available.
So If a width of 25% is selected, only 4 alignments (Left, Center-Left, Center-Right, Right) would be available.
Is that a better explanation?

1 Like

Are you able to help me?

I’ve been seriously distracted by a lockdown. I will come back to this in the next day or two.

2 Likes

no problem, stay safe.
Thanks…

There are many different ways to solve this particular issue. For example, all of the options could be removed from the HTML code with only the JavaScript generating what is needed.

However, even though this is the JavaScript forum we must never lose sight of the no-JavaScript situations. But, Everyone has JavaScript, right? No they bloody well do not, as is demonstrated nicely at that link. It’s important that we accommodate people without it as much as possible.

Another benefit of improving the HTML side of things, is that it often leads to better JavaScript solutions too.

Improve the HTML-only approach

Currently the options list is too cluttered for someone to easily make sense of it. Adding in some category options to help people more easily understand the groupings, is a good approach here.

    <select id="myDropdown">
        <option value="" disabled>100% Options</option>
        <option value="0" data-imagesrc="images/Alignment_0.png">None</option>
        <option value="" disabled>50% Options</option>
        <option value="1" data-imagesrc="images/Alignment_1.png">Left</option>
        <option value="2" data-imagesrc="images/Alignment_2.png">Right</option>
        <option value="" disabled>33% Options</option>
        <option value="3" data-imagesrc="images/Alignment_3.png">Left</option>
        <option value="4" data-imagesrc="images/Alignment_4.png">Center</option>
        <option value="5" data-imagesrc="images/Alignment_5.png">Right</option>
        <option value="" disabled>25% Options</option>
        <option value="6" data-imagesrc="images/Alignment_6.png">Left</option>
        <option value="7" data-imagesrc="images/Alignment_7.png">Center-Left</option>
        <option value="8" data-imagesrc="images/Alignment_8.png">Center-Right</option>
        <option value="9" data-imagesrc="images/Alignment_9.png">Right</option>
    </select>

I also fixed up what looked to be a mistake in the values, because from 7 the numbers stopped matching the image numbers.

That now lets someone without JavaScript select 50% from the first select, then go to the second select and easily find the 50% options to choose from.

My next post will be about how we communicate to JavaScript about which options belong to what category. It might be different from what you may think, too.

1 Like

When the first select changes, the usual approach is to hide all of the options, and then only reveal the appropriate ones. There are two main approaches that we can take in regard to revealing them.

We can either:

  1. Scan for a matching option heading and only reveal the options until the next option heading
  2. Or, give each option a category tag, and reveal each option with a matching category.

Because we have option headings in the list, the first option is certainly tempting, but it results in complicated and easily broken code, so it’s best to not go there.

The second option where we tell each option what category it belongs to, is what I want to do here today, but your use of ddslick destroys the original select options.

I was really looking forward to having a data-width="50%" attribute on each option, but that is not to be.

The worse and first choice is what we must do today instead, where we scan for a matching option heading, and reveal things from there to the next option heading.

It’s just gone past breakfast and I already need a drink.

What do we need to do to hide one of the ddslick options?

How to hide ddSlick options?

Here’s one of the options before ddSlick gets a hold of it:

        <option value="1" data-imagesrc="images/Alignment_1.png">Left</option>

ddslick results in the following code for the above option:

<li>
  <a class="dd-option"> 
    <input class="dd-option-value" type="hidden" value="1"> 
    <img class="dd-option-image" src="images/Alignment_1.png"> 
    <label class="dd-option-text" style="line-height: 18px;">Left</label>
  </a>
</li>

It would be nice if we could search for the dd-option classname and hide that. Does that work?

.hide {
    display: none;
 }
  <a class="dd-option hide"> 

No, that doesn’t work because the dd-option class has a display=block attribute that overrules hide.

Can I use !important to overrule the dd-option one? I can hear CSS people yelling at me already that I don’t know what I’m doing. Okay okay, enough. On to a better idea.

I’ll update the hide rule so that it has a higher specificity as the dd-option one.

.dd-option.hide {
    display: none;
 }

And now that we have a relatively easy way to hide options, we can work on the JavaScript code.

Change handler

First, I want the change handler to get the information that it needs, and pass control to a separate dedicated function to show the alignment options.

I renamed the handler too, to be more consistent with naming conventions.

    function widthChangeHandler(evt) {
        var widthChoice = evt.target;
        var alignmentOptions = d.querySelector("#myDropdocwn");
        showAlignmentOptions(widthChoice, alignmentOptions);
    }
    var orientation = d.querySelector("#Width");
    orientation.addEventListener("change", widthChangeHandler);

That width identifier though really should start with a lowercase letter.

<!--<select id="Width">-->
    <select id="width">
    // var orientation = d.querySelector("#Width");
    var orientation = d.querySelector("#width");

And that myDropdown really is a bloody terrible name.

Renaming myDropdown to alignments

Now is a good time to improve the naming of that select.

<!--<select id="myDropdown">-->
    <select id="alignments">
    // $('#myDropdown').ddslick({
    $('#alignments').ddslick({
    });
    ...
    function widthChangeHandler(evt) {
        var widthChoice = evt.target;
        // var alignmentOptions = d.querySelector("#myDropdown");
        var alignmentOptions = d.querySelector("#alignments");
        showAlignmentOptions(widthChoice, alignmentOptions);
    }

We can now work on the showAlignmentOptions, where we have good and easy access to both of the select lists.

I need another virtual drink at this stage though, so it’s time to take a mental break while I ponder over whether to try and demonstrate coding things up properly and perfectly, or do it poorly and improve from there.

1 Like

I don’t want to be the equivalent of a cooking show, saying “And here’s one I prepared earlier!”. And, I think that more value is gained from seeing it done poorly, and improving on it from there.
Also, TDD techniques have you solve it quickly with bad code, giving you green tests and letting you then improve the code while maintaining things in the green.

As a result, it’s time to do things poorly, for now.

First of all, we need to hide the existing options.

Hide alignment options

I don’t think that this part will change throughout the improvements. What we need to do is to get the different options, and hide each of them.

    function showAlignmentOptions(widthSelect, alignments) {
        var options = alignments.querySelectorAll(".dd-option");
        options.forEach(function (option) {
            option.classList.add("hide");
        });
    }

We are now in a good place to show relevant options, poorly at first.

A poor way to show alignment options, but it works

An initial start is to get the text of the chosen width, and use that to decide which options to show.

        var widthOption = widthSelect.options[widthSelect.selectedIndex];
        var widthChoice = widthOption.text;
        if (widthChoice === "100%") {
            options[1].classList.remove("hide");
        }
        if (widthChoice === "50%") {
            options[3].classList.remove("hide");
            options[4].classList.remove("hide");
        }
        if (widthChoice === "33%") {
            options[6].classList.remove("hide");
            options[7].classList.remove("hide");
            options[8].classList.remove("hide");
        }
        if (widthChoice === "25%") {
            options[10].classList.remove("hide");
            options[11].classList.remove("hide");
            options[12].classList.remove("hide");
            options[13].classList.remove("hide");
        }

That works (and desperately need improving), but the currently selected option doesn’t update. We can use the ddSlick select method, to select an option.

I tried by having it select the first available option each time, but most of those are just Left and don’t provide much visual feedback.

Instead, I selected the option heading, which gives much better “50% Options” feedback each time.

Fixing remaining issues

A few other problems are noticed, such as that the second dropdown is fully populated before a selection is made. We need to hide those selections on page load, as well as when changes are made.

We should move that hiding code to a separate function, so that we can call it from different places.

It is important that we know that the hide function is only relevant to hiding ddslick options, so I’ve included that in the function name.

    function hideDDSlickOptions(select) {
        var options = alignments.querySelectorAll(".dd-option");
        options.forEach(function (option) {
            option.classList.add("hide");
        });
    }
    function showAlignmentOptions(widthSelect, alignments) {
        // var options = alignments.querySelectorAll(".dd-option");
        // options.forEach(function (option) {
        //     option.classList.add("hide");
        // });
        hideDDSlickOptions(alignments);
        var options = alignments.querySelectorAll(".dd-option");
        ...
    }
    ...
    var alignments = d.querySelector("#alignments");
    hideDDSlickOptions(alignments);

That second call to hideDDSlickOptions ensures that they are hidden on page load too, which has revealed two new problems, one in the code and one on the page.

Having only one alignment assignment

The code problem is that alignments is being assigned twice, once on pageload and also in the event handler. That alignments variable should be moved up to the top of the code, so that it’s available to all of the code beneath. We should also do that with the width select one too.

(function(d) {
    "use strict";
    var widths = d.querySelector("#width");
    var alignments = d.querySelector("#alignments");

    ...
    
    function widthChangeHandler(evt) {
        var widthChoice = evt.target;
        // var alignments = d.querySelector("#alignments");
        showAlignmentOptions(widthChoice, alignments);
    }
    // var orientation = d.querySelector("#width");
    // orientation.addEventListener("change", widthChangeHandler);
    widths.addEventListener("change", widthChangeHandler);

    // var alignments = d.querySelector("#alignments");
    hideDDSlickOptions(alignments);

Doing that lets us see that the assignment of widths and alignments are inconsistent, so I’ll make them singular instead.

<select id="width">
...
<select id="alignment">
// $('#alignments').ddslick({
$('#alignment').ddslick({
});
...
    var widths = d.querySelector("#width");
    var alignments = d.querySelector("#alignment");

Showing a suitable message on page load

The other problem that occurs on the page, is that on pageload the second select doesn’t have an appropriate message. It really should have a message that says: “Please select a Width”.

The ddSlick code does have a selectText message, but that only seems to work when you give it the options as a JSON data set.

So instead, we can do the usual trick of having the first option be the message.

<select id="alignment">
    <option value="" disabled>Please select a Width</option>

And at the end of the JavaScript code where the options are hidden, have it select that first option to be shown.

    hideDDSlickOptions(alignments);
    $(alignments).ddslick('select', {index: 0});
}(document));

But, adding that option changes all of the option indexes, so we have to go back and update them all. I’ve also moved the option heading selection before showing the options, so that the numbered indexes remain in appropriate order.

        if (widthChoice === "100%") {
            $(alignments).ddslick('select', {index: 1});
            options[2].classList.remove("hide");
        }
        if (widthChoice === "50%") {
            $(alignments).ddslick('select', {index: 3});
            options[4].classList.remove("hide");
            options[5].classList.remove("hide");
        }
        if (widthChoice === "33%") {
            $(alignments).ddslick('select', {index: 6});
            options[7].classList.remove("hide");
            options[8].classList.remove("hide");
            options[9].classList.remove("hide");
        }
        if (widthChoice === "25%") {
            $(alignments).ddslick('select', {index: 10});
            options[11].classList.remove("hide");
            options[12].classList.remove("hide");
            options[13].classList.remove("hide");
            options[14].classList.remove("hide");
        }

This is just some of the pain that comes from doing things poorly. Despite that though it has allowed us to fix many other problems, and we now have useful information about what needs to be improved from here, which will occur in my next post.

2 Likes

Example code for what has been currently done is up at https://jsfiddle.net/pmw57/8ycv79qs/