Semantic-UI icon indicating copy to clipboard operation
Semantic-UI copied to clipboard

[Dropdown] Convert <optgroup> to headers

Open zelenin opened this issue 9 years ago • 28 comments

Now Semantic UI not parses optgroups in select creating a dropdown like flat select.

I offer display optgroups like a dropdown header. See jsfiddle:

http://jsfiddle.net/zelenin/aLoanbwb/1/

zelenin avatar Jan 27 '15 22:01 zelenin

:+1: It's no good that the headers totally disappear when using the Semantic select dropdown.

mroach avatar Feb 24 '15 00:02 mroach

+1 would be nice improvement

georges avatar Mar 26 '15 15:03 georges

Use Divider may be better than header.

http://jsfiddle.net/aLoanbwb/3/

74sharlock avatar Sep 24 '15 09:09 74sharlock

The coding concern is here is JS search dropdowns aren't built to hide headers for empty groups. I don't think we can add this feature without considering filtering empty groups.

jlukic avatar Sep 25 '15 21:09 jlukic

I made a workaround using jquery:

$(function () {            
            // clear the generated semantic-ui menu
            $('.menu').html("");
            // add the head items based on the optgroup and the items based on the options
            $('optgroup').each(function (index, element) {
                $('.menu').append('<div class="header">' + element.label + '</div>')
                $(element).children().each(function(i, e){
                    $('.menu').append('<div class="item" data-value="' + e.value + '">' + e.innerHTML + '</div>');
                })
            })
        });

brunotourinho avatar Sep 28 '15 16:09 brunotourinho

Keep in mind @brunotourinho if you are doing dom insertion you should work on a document fragment. DOM insertion is very costly.

$(function () {            
    // clear the generated semantic-ui menu
    var $menu = $('<div/>').addClass('menu');
    // add the head items based on the optgroup and the items based on the options
    $('optgroup').each(function (index, element) {
        $menu.append('<div class="header">' + element.label + '</div>')
        $(element).children().each(function(i, e){
            $menu.append('<div class="item" data-value="' + e.value + '">' + e.innerHTML + '</div>');
        })
    });
    $('.menu').replaceWith($menu);
});

jlukic avatar Sep 28 '15 23:09 jlukic

Thank you @jlukic !!! ^^

brunotourinho avatar Sep 29 '15 11:09 brunotourinho

Hey @jlukic, I tested your code and got some animation error:

Transition: There is no css animation matching the one you specified. Please make sure your css is vendor prefixed, and you have included transition css. slide down in

<div class=​"menu" tabindex=​"-1">​…​</div>​

e.fn.transition.a.each.C.error  @   semantic.min.js:19
e.fn.transition.a.each.C.animate    @   semantic.min.js:19
e.fn.transition.a.each.C.initialize @   semantic.min.js:19
(anonymous function)    @   semantic.min.js:19
jQuery.extend.each  @   jquery-1.10.2.js:671
jQuery.fn.jQuery.each   @   jquery-1.10.2.js:280
e.fn.transition @   semantic.min.js:19
e.fn.dropdown.r.each.w.animate.show @   semantic.min.js:14
e.fn.dropdown.r.each.w.show @   semantic.min.js:13
e.fn.dropdown.r.each.w.toggle   @   semantic.min.js:13
e.fn.dropdown.r.each.w.determine.eventOnElement @   semantic.min.js:14
e.fn.dropdown.r.each.w.event.test.toggle    @   semantic.min.js:13
jQuery.event.dispatch   @   jquery-1.10.2.js:5109
jQuery.event.add.elemData.handle    @   jquery-1.10.2.js:4780

brunotourinho avatar Sep 29 '15 12:09 brunotourinho

Sorry replacewith would remove events on $menu. I should have checked

Use

// clear the generated semantic-ui menu
var $menu = $('<div/>').addClass('menu');
// add the head items based on the optgroup and the items based on the options
$('optgroup').each(function (index, element) {
    $menu.append('<div class="header">' + element.label + '</div>')
    $(element).children().each(function(i, e){
        $menu.append('<div class="item" data-value="' + e.value + '">' + e.innerHTML + '</div>');
    })
});
$('.dropdown .menu').html($menu.html());

http://jsfiddle.net/mLuxeLu3/

jlukic avatar Sep 29 '15 15:09 jlukic

Hello! Works great! Now just a little issue, on your example the scrollbar is offset to 1 (or so), hiding the first header. I couldn't reset using scrollTop(0)

brunotourinho avatar Sep 30 '15 13:09 brunotourinho

:+1: Currently a limitation for what I need..

Also consider how the label would appear for a multi-select select. Would the label include the optgroup label? In the case below, would the label text be; "Letters - B", "Letters - C", and "Numbers - 1"?? Or perhaps some sort of divider between the group labels.?.

<select multiple class="ui dropdown">
    <optgroup label="Letters">
        <option>A</option>
        <option selected>B</option>
        <option selected>C</option>
    </optgroup>
    <optgroup label="Numbers">
        <option selected>1</option>
        <option>2</option>
        <option>3</option>
    </optgroup>
</select>

Panman82 avatar Nov 17 '15 19:11 Panman82

In order to be the closest from the original (browsers select) behavior, shouldn't we want to keep an "group container element" ? If that so, I would suggest as the result something like this :

<div class="menu">
    <div class="ui basic segment">
        <div class="header">Optgroup 1</div>
        <div class="item">Option 1</div>
        <div class="item">Option 2</div>
    </div>
    <div class="ui basic segment">
        <div class="header">Optgroup 2</div>
        <div class="item">Option 3</div>
        <div class="item">Option 4</div>
    </div>
</div>

Main benefit : being able to select, filter or discriminate the options from a group or an other (through extra ids or attributes). Main drawback : it breaks the design since the css rule requires an item to be a direct child of a menu

challet avatar Jul 25 '16 16:07 challet

@jlukic Great work bro, I was looking at this issue and reached your code and it's worked well.

merakeshvk avatar Jan 25 '17 11:01 merakeshvk

@jlukic when we have multiple dropdowns this optgroup is replacing other dropdowns. please check it and appreciate if you could fix this issue.

merakeshvk avatar Jan 25 '17 13:01 merakeshvk

@jlukic your code is working fine but still have one problem in it. if you see the attached image "option1" is selected by default by dropdown but not marked as "active selected" for that option.

image

harisahmed11 avatar Jan 26 '17 10:01 harisahmed11

@merakeshvk here is solution for you :P

var $menu = $('<div/>').addClass('menu');
$('.opt-group').each(function(){
    debugger
    var parentthis=$(this);
    $(this).find('optgroup').each(function (index, element) {
       $menu.append('<div class="header">' + element.label + '</div>')
       $(element).children().each(function(i, e){
           $menu.append('<div class="item" data-value="' + e.value + '">' + e.innerHTML + '</div>');
       })
    })
    $(this).find('.menu').html($menu.html());
    $menu="";
    $menu = $('<div/>').addClass('menu');
});

harisahmed11 avatar Jan 26 '17 10:01 harisahmed11

@harisahmed11 Thanks bro, Good work.

merakeshvk avatar Jan 26 '17 10:01 merakeshvk

Fixed Issue by @brunotourinho It's 2017 now but sharing it for somebody like me.

const menu = jQuery('<div/>').addClass('menu');
  menu.append('<div class="item" style="display: none"></div>');
    jQuery(item).children('optgroup').each((index, element: any) => {
      menu.append('<div class="header">' + element.label + '</div>');
      jQuery(element).children().each((i, e: any) => {
        menu.append('<div class="item" data-value="' + e.value + '">' + e.innerHTML + '</div>');
      });
    });
    jQuery(item).siblings('.menu').html(menu.html());
}

Written in typescript, but I believe code is simple enough to convert for your project. It works by appending hidden item in front of menu.

icepeng avatar Mar 16 '17 05:03 icepeng

Hi all! Based on @jlukic's answer, I wrote a generic code snippet that:

  • works fine even with multiple dropdowns in your page
  • does not break dropdowns without <optgroup>

CoffeeScript:

# Hack for Semantic UI multiple select with <optgroup> support
$('.ui.dropdown').has('optgroup').each ->
  $menu = $('<div/>').addClass('menu')

  # Recreate the dropdown menu with header items for optgroups and normal items for options
  $(this).find('optgroup').each ->
    $menu.append("<div class=\"header\">#{this.label}</div><div class=\"divider\"></div>")
    $(this).children().each ->
      $menu.append("<div class=\"item\" data-value=\"#{this.value}\">#{this.innerHTML}</div>")

  $(this).find('.menu').html($menu.html())

JavaScript:

$('.ui.dropdown').has('optgroup').each(function() {
  var $menu;
  $menu = $('<div/>').addClass('menu');
  $(this).find('optgroup').each(function() {
    $menu.append("<div class=\"header\">" + this.label + "</div><div class=\"divider\"></div>");
    return $(this).children().each(function() {
      return $menu.append("<div class=\"item\" data-value=\"" + this.value + "\">" + this.innerHTML + "</div>");
    });
  });
  return $(this).find('.menu').html($menu.html());
});

Demonstration: http://jsfiddle.net/hqqpvohL

😉

(However, there is still a tiny glitch: when you open a long dropdown, the focus goes on the first option, not on the first group label, as mentioned in #5099)

tristanjahier avatar May 05 '17 18:05 tristanjahier

You can also make a disabled element as category header. This also looks nice with semantic-ui.

Like this:

<select id="category" class="ui selection dropdown">
	<option disabled>Category 1</option>
		<option value="1">&nbsp;&nbsp; - Menu 1</option>
		<option value="2">&nbsp;&nbsp; - Menu 2</option>
	<option disabled>Category 2</option>
		<option value="3">&nbsp;&nbsp; - Menu 3</option>
		<option value="4">&nbsp;&nbsp; - Menu 4</option>
</select>

slawkens avatar Nov 09 '17 13:11 slawkens

Modified the code by @tristanjahier a bit so selected option would by highlighted.

$('.ui.dropdown').has('optgroup').each(function() {
    const $menu = $('<div/>').addClass('menu');
    $(this).find('optgroup').each(function() {
        $menu.append("<div class=\"header\">" + this.label + "</div><div class=\"divider\"></div>");
        return $(this).children().each(function() {
            return $menu.append("<div class=\"item" + (this.selected ? ' active selected' : '') +  "\" data-value=\"" + this.value + "\">" + this.innerHTML + "</div>");
        });
    });
    return $(this).find('.menu').html($menu.html());
});

gbasov avatar Jan 24 '18 23:01 gbasov

This issue has been automatically marked as stale because it has not had recent activity. It will be closed in 30 days if no further activity occurs. Thank you for your contributions.

stale[bot] avatar Apr 24 '18 23:04 stale[bot]

Bump, due to stale bot. Still a valid request.

Panman82 avatar Apr 25 '18 12:04 Panman82

Small caveat with @genabasov latest iteration of the code, it removes any separate 'option' that might have been in the select.

Fixing this issue in the actual dropdown.js seems to be a lot more complicated than I initially expected, so I've updated the code to also handle any individual 'option' and also respect the disabled status for options and optgroups while also prioritizing any label over text as defined here.

$('.ui.dropdown').has('optgroup').each(function() {
    const $menu = $('<div/>').addClass('menu');
    $(this).find('select').children().each(function() {
	if($(this).is('option')) {
	    return $menu.append('<div class="item' + (this.selected ? ' active selected' : '') + (this.disabled ? ' disabled' : '') + '" data-value="' + this.value + '">' + (this.label || this.innerHTML) + "</div>");
	}
	if($(this).is('optgroup')) {
	    var isDisabled = this.disabled || false;
	    $menu.append('<div class="header' + (isDisabled ? ' item disabled' : '') + '">' + this.label + '</div>');
	    //$menu.append('<div class="divider"></div>');
	    $(this).children().each(function() {
		return $menu.append('<div class="item' + (this.selected ? ' active selected' : '') + (isDisabled || this.disabled ? ' disabled' : '') + '" data-value="' + this.value + '">' + (this.label || this.innerHTML) + "</div>");
	    });
	    return $menu;
	}
    });
    return $(this).find('.menu').html($menu.html());
});

Wiethoofd avatar May 08 '18 11:05 Wiethoofd

@zelenin: What if I want to use option as an object instead to achieve grouping ? I have dynamic options so I can't really use option tags. I work with semantic ui react.

shabnamshariaty avatar May 17 '18 19:05 shabnamshariaty

I've added the group label to the option's data-text attribute (data-text=": "). The benefit is the the tags contain also the group label and searching for the group label shows also all the children.

$('.ui.dropdown').has('optgroup').each(function () {
    var $menu = $('<div/>').addClass('menu');
    $(this).find('select').children().each(function () {
        if ($(this).is('option')) {
            return $menu.append('<div class="item' + (this.selected ? ' active selected' : '') + (this.disabled ? ' disabled' : '') + '" data-value="' + this.value + '">' + (this.label || this.innerHTML) + "</div>");
        }
        if ($(this).is('optgroup')) {
            var isDisabled = this.disabled || false;
            var groupLabel = this.label;
            $menu.append('<div class="header' + (isDisabled ? ' item disabled' : '') + '">' + groupLabel + '</div>');
            $(this).children().each(function () {
                return $menu.append('<div class="item' + (this.selected ? ' active selected' : '') + (isDisabled || this.disabled ? ' disabled' : '') + '" data-value="' + this.value + '" data-text="' + groupLabel + ': ' + this.label + '">' + (this.label || this.innerHTML) + "</div>");
            });
            return $menu;
        }
    });
    return $(this).find('.menu').html($menu.html());
});

ronnievdc avatar Sep 26 '18 14:09 ronnievdc

optgroups are now converted to headers in Fomantic-UI by https://github.com/fomantic/Fomantic-UI/pull/957

lubber-de avatar Aug 26 '19 19:08 lubber-de

base on @tristanjahier :

$('.ui.dropdown').has('optgroup').each(function() {
    const $menu = $('<div/>').addClass('menu');
    $(this).find('optgroup').each(function() {
        $menu.append("<div class=\"ui horizontal divider\"><div class=\"header\">" + this.label + "</div></div>");
        return $(this).children().each(function() {
            return $menu.append("<div class=\"item" + (this.selected ? ' active selected' : '') +  "\" data-value=\"" + this.value + "\">" + this.innerHTML + "</div>");
        });
    });
    return $(this).find('.menu').html($menu.html());
});

$('.ui.dropdown .menu>.divider').css('border-top','none');

zikezhang avatar Feb 25 '22 15:02 zikezhang