Semantic-Org / Semantic-UI

Semantic is a UI component framework based around useful principles from natural language.
http://www.semantic-ui.com
MIT License
51.1k stars 4.95k forks source link

[Dropdown] Convert <optgroup> to headers #1727

Open zelenin opened 9 years ago

zelenin commented 9 years ago

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/

mroach commented 9 years ago

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

georges commented 9 years ago

+1 would be nice improvement

74sharlock commented 9 years ago

Use Divider may be better than header.

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

jlukic commented 8 years ago

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.

brunotourinho commented 8 years ago

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>');
                })
            })
        });
jlukic commented 8 years ago

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);
});
brunotourinho commented 8 years ago

Thank you @jlukic !!! ^^

brunotourinho commented 8 years ago

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
jlukic commented 8 years ago

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/

brunotourinho commented 8 years ago

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)

Panman82 commented 8 years ago

:+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>
challet commented 8 years ago

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

merakeshvk commented 7 years ago

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

merakeshvk commented 7 years ago

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

harisahmed11 commented 7 years ago

@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 commented 7 years ago

@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');
});
merakeshvk commented 7 years ago

@harisahmed11 Thanks bro, Good work.

icepeng commented 7 years ago

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.

tristanjahier commented 7 years ago

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

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)

slawkens commented 6 years ago

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>
gbasov commented 6 years ago

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());
});
stale[bot] commented 6 years ago

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.

Panman82 commented 6 years ago

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

Wiethoofd commented 6 years ago

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());
});
shabnamshariaty commented 6 years ago

@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.

ronnievdc commented 5 years ago

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());
});
lubber-de commented 5 years ago

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

zikezhang commented 2 years ago

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');