Question

I have created a customer c# DropDownList control that can render out it's contents are optgroups (Not from scratch, I edited some code found on the internet, although I do understand exactly what it's doing), and it works fine.

However, I have now come across a situation where I need to have two levels of indentation in my dropdown, i.e.

<select>
  <optgroup label="Level One">
    <option> A.1 </option>
    <optgroup label="Level Two">
      <option> A.B.1 </option>
    </optgroup>
    <option> A.2 </option>
  </optgroup>
</select>

However, in the example snippet above, it is rendering as if Level Two was at the same amount of indentation as Level One.

Is there a way to produce the nested optgroup behavior I am looking for?

Was it helpful?

Solution 3

Ok, if anyone ever reads this: the best option is to add four &nbsp;s at each extra level of indentation, it would seem!

so:

<select>
 <optgroup label="Level One">
  <option> A.1 </option>
  <optgroup label="&nbsp;&nbsp;&nbsp;&nbsp;Level Two">
   <option>&nbsp;&nbsp;&nbsp;&nbsp; A.B.1 </option>
  </optgroup>
  <option> A.2 </option>
 </optgroup>
</select>

OTHER TIPS

The HTML spec here is really broken. It should allow nested optgroups and recommend user agents render them as nested menus. Instead, only one optgroup level is allowed. However, they do have to say the following on the subject:

Note. Implementors are advised that future versions of HTML may extend the grouping mechanism to allow for nested groups (i.e., OPTGROUP elements may nest). This will allow authors to represent a richer hierarchy of choices.

And user agents could start using submenus to render optgoups instead of displaying titles before the first option element in an optgroup as they do now.

This is just fine but if you add option which is not in optgroup it gets buggy.

<select>
  <optgroup label="Level One">
    <option> A.1 </option>
    <optgroup label="&nbsp;&nbsp;&nbsp;&nbsp;Level Two">
      <option>&nbsp;&nbsp;&nbsp;&nbsp; A.B.1 </option>
    </optgroup>
    <option> A.2 </option>
  </optgroup>
  <option> A </option>
</select>

Would be much better if you used css and close optgroup right away :

<select>
  <optgroup label="Level One"></optgroup>
  <option style="padding-left:15px"> A.1 </option>
  <optgroup label="Level Two" style="padding-left:15px"></optgroup>
  <option style="padding-left:30px"> A.B.1 </option>
  <option style="padding-left:15px"> A.2 </option>
  <option> A </option>
</select>

<style>
    .NestedSelect{display: inline-block; height: 100px; border: 1px Black solid; overflow-y: scroll;}
    .NestedSelect label{display: block; cursor: pointer;}
    .NestedSelect label:hover{background-color: #0092ff; color: White;}
    .NestedSelect input[type="radio"]{display: none;}
    .NestedSelect input[type="radio"] + span{display: block; padding-left: 0px; padding-right: 5px;}
    .NestedSelect input[type="radio"]:checked + span{background-color: Black; color: White;}
    .NestedSelect div{margin-left: 15px; border-left: 1px Black solid;}
    .NestedSelect label > span:before{content: '- ';}
</style>

<div class="NestedSelect">
    <label><input type="radio" name="MySelectInputName"><span>Fruit</span></label>
    <div>
        <label><input type="radio" name="MySelectInputName"><span>Apple</span></label>
        <label><input type="radio" name="MySelectInputName"><span>Banana</span></label>
        <label><input type="radio" name="MySelectInputName"><span>Orange</span></label>
    </div>

    <label><input type="radio" name="MySelectInputName"><span>Drink</span></label>
    <div>
        <label><input type="radio" name="MySelectInputName"><span>Water</span></label>

        <label><input type="radio" name="MySelectInputName"><span>Soft</span></label>
        <div>
            <label><input type="radio" name="MySelectInputName"><span>Cola</span></label>
            <label><input type="radio" name="MySelectInputName"><span>Soda</span></label>
            <label><input type="radio" name="MySelectInputName"><span>Lemonade</span></label>
        </div>

        <label><input type="radio" name="MySelectInputName"><span>Hard</span></label>
        <div>
            <label><input type="radio" name="MySelectInputName"><span>Bear</span></label>
            <label><input type="radio" name="MySelectInputName"><span>Whisky</span></label>
            <label><input type="radio" name="MySelectInputName"><span>Vodka</span></label>
            <label><input type="radio" name="MySelectInputName"><span>Gin</span></label>
        </div>
    </div>
</div>

I think if you have something that structured and complex, you might consider something other than a single drop-down box.

I really like the Broken Arrow's solution above in this post. I have just improved/changed it a bit so that what was called labels can be toggled and are not considered options. I have used a small piece of jQuery, but this could be done without jQuery.

I have replaced intermediate labels (no leaf labels) with links, which call a function on click. This function is in charge of toggling the next div of the clicked link, so that it expands/collapses the options. This avoids the possibility of selecting an intermediate element in the hierarchy, which usually is something desired. Making a variant that allows to select intermediate elements should be easy.

This is the modified html:

<div class="NestedSelect">
    <a onclick="toggleDiv(this)">Fruit</a>
    <div>
        <label>
            <input type="radio" name="MySelectInputName"><span>Apple</span></label>
        <label>
            <input type="radio" name="MySelectInputName"><span>Banana</span></label>
        <label>
            <input type="radio" name="MySelectInputName"><span>Orange</span></label>
    </div>

    <a onclick="toggleDiv(this)">Drink</a>
    <div>
        <label>
            <input type="radio" name="MySelectInputName"><span>Water</span></label>

        <a onclick="toggleDiv(this)">Soft</a>
        <div>
            <label>
                <input type="radio" name="MySelectInputName"><span>Cola</span></label>
            <label>
                <input type="radio" name="MySelectInputName"><span>Soda</span></label>
            <label>
                <input type="radio" name="MySelectInputName"><span>Lemonade</span></label>
        </div>

        <a onclick="toggleDiv(this)">Hard</a>
        <div>
            <label>
                <input type="radio" name="MySelectInputName"><span>Bear</span></label>
            <label>
                <input type="radio" name="MySelectInputName"><span>Whisky</span></label>
            <label>
                <input type="radio" name="MySelectInputName"><span>Vodka</span></label>
            <label>
                <input type="radio" name="MySelectInputName"><span>Gin</span></label>
        </div>
    </div>
</div>

A small javascript/jQuery function:

function toggleDiv(element) {
    $(element).next('div').toggle('medium');
}

And the css:

.NestedSelect {
    display: inline-block;
    height: 100%;
    border: 1px Black solid;
    overflow-y: scroll;
}

.NestedSelect a:hover, .NestedSelect span:hover  {
    background-color: #0092ff;
    color: White;
    cursor: pointer;
}

.NestedSelect input[type="radio"] {
    display: none;
}

.NestedSelect input[type="radio"] + span {
    display: block;
    padding-left: 0px;
    padding-right: 5px;
}

.NestedSelect input[type="radio"]:checked + span {
    background-color: Black;
    color: White;
}

.NestedSelect div {
    display: none;
    margin-left: 15px;
    border-left: 1px black
    solid;
}

.NestedSelect label > span:before, .NestedSelect a:before{
    content: '- ';
}

.NestedSelect a {
    display: block;
}

Running sample in JSFiddle

I needed clean and lightweight solution (so no jQuery and alike), which will look exactly like plain HTML, would also continue working when only plain HTML is preset (so javascript will only enhance it), and which will allow searching by starting letters (including national UTF-8 letters) if possible where it does not add extra weight. It also must work fast on very slow browsers (think rPi - so preferably no javascript executing after page load).

In firefox it uses CSS identing and thus allow searching by letters, and in other browsers it will use &nbsp; prepending (but there it does not support quick search by letters). Anyway, I'm quite happy with results.

You can try it in action here

It goes like this:

CSS:

.i0 { }
.i1 { margin-left: 1em; }
.i2 { margin-left: 2em; }
.i3 { margin-left: 3em; }
.i4 { margin-left: 4em; }
.i5 { margin-left: 5em; }

HTML (class "i1", "i2" etc denote identation level):

<form action="/filter/" method="get">
<select name="gdje" id="gdje">
<option value=1 class="i0">Svugdje</option>
<option value=177 class="i1">Bosna i Hercegovina</option>
<option value=190 class="i2">Babin Do</option>  
<option value=258 class="i2">Banja Luka</option>
<option value=181 class="i2">Tuzla</option>
<option value=307 class="i1">Crna Gora</option>
<option value=308 class="i2">Podgorica</option>
<option value=2 SELECTED class="i1">Hrvatska</option>
<option value=5 class="i2">Bjelovarsko-bilogorska županija</option>
<option value=147 class="i3">Bjelovar</option>
<option value=79 class="i3">Daruvar</option>  
<option value=94 class="i3">Garešnica</option>
<option value=329 class="i3">Grubišno Polje</option>
<option value=368 class="i3">Čazma</option>
<option value=6 class="i2">Brodsko-posavska županija</option>
<option value=342 class="i3">Gornji Bogićevci</option>
<option value=158 class="i3">Klakar</option>
<option value=140 class="i3">Nova Gradiška</option>
</select>
</form>

<script>
<!--
        window.onload = loadFilter;
// -->   
</script>

JavaScript:

function loadFilter() {
  'use strict';
  // indents all options depending on "i" CSS class
  function add_nbsp() {
    var opt = document.getElementsByTagName("option");
    for (var i = 0; i < opt.length; i++) {
      if (opt[i].className[0] === 'i') {
      opt[i].innerHTML = Array(3*opt[i].className[1]+1).join("&nbsp;") + opt[i].innerHTML;      // this means "&nbsp;" x (3*$indent)
      }
    }
  }
  // detects browser
  navigator.sayswho= (function() {
    var ua= navigator.userAgent, tem,
    M= ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\/))\/?\s*([\d\.]+)/i) || [];
    if(/trident/i.test(M[1])){
        tem=  /\brv[ :]+(\d+(\.\d+)?)/g.exec(ua) || [];
        return 'IE '+(tem[1] || '');
    }
    M= M[2]? [M[1], M[2]]:[navigator.appName, navigator.appVersion, '-?'];
    if((tem= ua.match(/version\/([\.\d]+)/i))!= null) M[2]= tem[1];
    return M.join(' ');
  })();
  // quick detection if browser is firefox
  function isFirefox() {
    var ua= navigator.userAgent,
    M= ua.match(/firefox\//i);  
    return M;
  }
  // indented select options support for non-firefox browsers
  if (!isFirefox()) {
    add_nbsp();
  }
}  

I know this was quite a while ago, however I have a little extra to add:

This is not possible in HTML5 or any previous specs, nor is it proposed in HTML5.1 yet. I have made a request to the public-html-comments mailing list, but we'll see if anything comes of it.

Regardless, whilst this is not possible using <select> yet, you can achieve a similar effect with the following HTML, plus some CSS for prettiness:

<ul>
  <li>
	<input type="radio" name="location" value="0" id="loc_0" />
	<label for="loc_0">United States</label>
	<ul>
	  <li>
		Northeast
        <ul>
	      <li>
			<input type="radio" name="location" value="1" id="loc_1" />
			<label for="loc_1">New Hampshire</label>
		  </li>
          <li>
			<input type="radio" name="location" value="2" id="loc_2" />
			<label for="loc_2">Vermont</label>
		  </li>
          <li>
			 <input type="radio" name="location" value="3" id="loc_3" />
			 <label for="loc_3">Maine</label>
		  </li>
		 </ul>
	   </li>
       <li>
		   Southeast
           <ul>
			 <li>
				<input type="radio" name="location" value="4" id="loc_4" />
				<label for="loc_4">Georgia</label>
			 </li>
             <li>
				<input type="radio" name="location" value="5" id="loc_5" />
				<label for="loc_5">Alabama</label>
			 </li>
		   </ul>
		</li>
	  </ul>
    </li>
    <li>
	   <input type="radio" name="location" value="6" id="loc_6" />
	   <label for="loc_6">Canada</label>
       <ul>
		  <li>
			 <input type="radio" name="location" value="7" id="loc_7" />
			 <label for="loc_7">Ontario</label>
		  </li>
          <li>
			  <input type="radio" name="location" value="8" id="loc_8" />
		      <label for="loc_8">Quebec</label>
		  </li>
          <li>
			   <input type="radio" name="location" value="9" id="loc_9" />
			   <label for="loc_9">Manitoba</label>
		  </li>
		</ul>
	 </li>
  </ul>

As an extra added benefit, this also means you can allow selection of the <optgroups> themselves. This might be useful if you had, for example, nested categories where the categories go into heavy detail and you want to allow users to select higher up in the hierarchy.

This will all work without JavaScript, however you might wish to add some to hide the radio buttons and then change the background color of the selected item or something.

Bear in mind, this is far from a perfect solution, but if you absolutely need a nested select with reasonable cross-browser compatibility, this is probably as close as you're going to get.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top