3

I would like to select a term from the dropdown and then select a color or value from the same term. For example, I want to select "Account Executive" and then select "red". Is there a jquery library already out there? Is it possible? It can also be a dropdown rather than the color.

enter image description here

This is how I populate the dropdown c#

  DataSet dsRoles = obj.ds("Role");
  ddlRole.DataSource = dsRoles ;
  ddlRole.DataTextField = "Term";
  ddlRole.DataValueField = "Term";
  ddlRole.DataBind();
  ddlRole.Items.Insert(0, new ListItem("== Select 1 Term ==", ""));

Asp

<asp:ListBox ID="ddlRole" AutoPostBack="false" runat="server" SelectionMode="Multiple"> 
Charles Xavier
  • 1,015
  • 3
  • 14
  • 33

3 Answers3

2

Unfortunately I do not know of anything out of the box that has that control. One of the more popular multi select jquery libraries out there is select2. You could override the js event for it to behave in the manner you are looking to accomplish. I am not sure how extensible this would be without some refactoring but here is a walk through on how to do it on a single select.

Link to CDN for select2: https://select2.org/getting-started/installation

First setup your basic select element

<select id="tag" style="width: 50%" multiple="multiple"></select>

The the fun begins in js

We will need to keep track of our selected values

    var collectionArray = [];

Next is out initial array to feed into our select2 control whereas id is the unique identifier of the position, simple text is the value you want to dispaly when selected, text is the html value in the dropdown

The text element has a your color blocks and each one has a on click element to pass the id and a numeric representation of the color into a js function. This should be blown out further but keeping it simple for demo

    var data = [
        { id: 0, simpleText:'Account Executive',  text: '<div style="display:block; min-height:30px; cursor:default;"><div style="float:left;">Account Executive</div><div style="float:right;"><table><tr><td class="tableSelect tableSelectRed" onclick="select2Override(0,0)"></td><td class="tableSelect tableSelectOrange" onclick="select2Override(0,1)"></td><td class="tableSelect tableSelectGreen"onclick="select2Override(0,2)"></td></tr></table></div></div>' },
        { id: 1,simpleText:'Account Management',  text: '<div style="display:block; min-height:30px; cursor:default;"><div style="float:left;">Account Management</div><div style="float:right;"><table><tr><td class="tableSelect tableSelectRed" onclick="select2Override(1,0)"></td><td class="tableSelect tableSelectOrange"onclick="select2Override(1,1)"></td><td class="tableSelect tableSelectGreen" onclick="select2Override(1,2)"></td></tr></table></div></div>' }];

Next we initialize our select2 object with our data array. We will need to override the selecting and unselecting functions and handle these on our own

$("#tag").select2({
        data: data,
        //these options are telling it to use html from the text element. They are required or else it will just generate your html as text
        templateResult: function (d) { return $(d.text); },
        templateSelection: function (d) { return $(d.text); },
        tags: true

    }).on("select2:selecting", function (e) {
        // make sure we are on the list and not within input box
        //this is overriding the default selection event of select2, we will hadle that in our own onclick function based upon the color box they selected
        if (e.params.args.originalEvent.currentTarget.nodeName === 'LI') {
            e.preventDefault();
        }
    }
    ).on('select2:unselecting', function (e) {
        //we are injecting another method here when the object is unselected. We want to remove it from our master list to prevent duplicate inputs on our add function
        removeFromList(e.params.args.data.id);
    });

We will need to manually handle the remove from our select list and make sure our master collection stays up to date

    function removeFromList(id) {
        //actual remove from the selected list
        $('#tag option[value=' + id + ']').remove();
        //actual remove from the master collection to prevent dups
        collectionArray = collectionArray.filter(entry => entry.tagID != id)
    }

Our main mover function for selection is next. We first need to check if the item already exists in our main collection (we dont want dups). If it does NOT then we need to give it a unique ID for later reference in our master list. We will find the max value from our current select list to make sure are not duplicating then inject the new value into our select list making sure it is marked as selected and alos into our master collection array:

    function select2Override(id, color) {
        //our override function for when anyone selects one of the color boxes

        //first check if it is already in our master list, if so then dont add a dup, remove it!
        var doesExistsAlready = collectionArray.filter(entry => entry.type === id && entry.color === color);
        if (doesExistsAlready.length != 0) {
            for (var i = 0; i < doesExistsAlready.length; i++) {
                removeFromList(doesExistsAlready[i].tagID);
            }

        } else {
            //its not found in our master list 
            //we need to get a unique if to accompy this entry, fund the highest existing value in the current list and add 1
            var lastID = findMaxValue($('#tag')) + 1;

            //push it to our master list
            collectionArray.push({ "type": id, "color": color, "tagID": lastID });

            //get the selected value from our initial list so we can pull out the "simple text" to display
            var check = $.grep(data, function (obj) { return obj.id === id; })[0];

            //decorate our selection with a color depending on what they selected
            var colorDisplay;
            switch(color) {
              case 0:
                    colorDisplay = "red";
                break;
              case 1:
                colorDisplay =  "orange"
                break;
              case 2:
                    colorDisplay =  "green";
                break;
            }
            //prep our new select option with our new color, simple text and unique id and set to selected
            var newOption = new Option('<div style="color:' + colorDisplay + ';display:inline;">' + check.simpleText + '</div>', lastID, true, true);

            //append it to our list and call our change method so js reacts to the change
            $('#tag').append(newOption).trigger('change');
        }
    }

Here is our JS function to make sure we have a unique ID from the select list items that already exists.

    //function to find the max value from our already existing list
    function findMaxValue(element) {
        var maxValue = undefined;
        $('option', element).each(function() {
            var val = $(this).attr('value');
            val = parseInt(val, 10);
            if (maxValue === undefined || maxValue < val) {
                maxValue = val;
            }
        });
        return maxValue;
    }

Lastly, we need to override some css so that items that we inject into our list are not shown for further selection. Luckily we can grab the disabled attributes that select2 uses for default behavior when an item is selected:

    .select2-container--default .select2-results__option[aria-selected=true] {
        display: none;
    }

Also, some css to make my hacked together html on the select element look semi presentable:

    .tableSelect {
        min-width: 20px;
        height: 20px;
        border: 1px solid #fff;
        cursor: pointer;
        margin-bottom: 10px;
    }

    .tableSelectGreen {
        background-color: green;
    }

    .tableSelectRed {
        background-color: red;
    }

    .tableSelectOrange {
        background-color: orange;
    }

    blockMe{
        min-height:20px;
        min-width:20px;
    }

Putting it all together:

 //this is where we will keep track of our selected values
        var collectionArray = [];

        //this is out initial array to feed into our select2 control
        //whereas id is the unique identifier of the position, simple text is the value you want to dispaly when selected, text is the html value in the dropdown
        //the text element has a your color blocks and each one has a on click element to pass the id and a numeric representation of the color into a js function. This blwon out further but keeping it simple for demo
        var data = [
            { id: 0, simpleText:'Account Executive',  text: '<div style="display:block; min-height:30px; cursor:default;"><div style="float:left;">Account Executive</div><div style="float:right;"><table><tr><td class="tableSelect tableSelectRed" onclick="select2Override(0,0)"></td><td class="tableSelect tableSelectOrange" onclick="select2Override(0,1)"></td><td class="tableSelect tableSelectGreen"onclick="select2Override(0,2)"></td></tr></table></div></div>' },
            { id: 1,simpleText:'Account Management',  text: '<div style="display:block; min-height:30px; cursor:default;"><div style="float:left;">Account Management</div><div style="float:right;"><table><tr><td class="tableSelect tableSelectRed" onclick="select2Override(1,0)"></td><td class="tableSelect tableSelectOrange"onclick="select2Override(1,1)"></td><td class="tableSelect tableSelectGreen" onclick="select2Override(1,2)"></td></tr></table></div></div>' }];

        //here we initialize our select2 object with our data array.
        $("#tag").select2({
            data: data,
            //these options are telling it to use html from the text element. They are required or else it will just generate your html as text
            templateResult: function (d) { return $(d.text); },
            templateSelection: function (d) { return $(d.text); },
            tags: true

        }).on("select2:selecting", function (e) {
            // make sure we are on the list and not within input box
            //this is overriding the default selection event of select2, we will hadle that in our own onclick function based upon the color box they selected
            if (e.params.args.originalEvent.currentTarget.nodeName === 'LI') {
                e.preventDefault();
            }
        }
        ).on('select2:unselecting', function (e) {
            //we are injecting another method here when the object is unselected. We want to remove it from our master list to prevent duplicate inputs on our add function
            removeFromList(e.params.args.data.id);
        });

        function removeFromList(id) {
            //actual remove from the selected list
            $('#tag option[value=' + id + ']').remove();
            //actual remove from the master collection to prevent dups
            collectionArray = collectionArray.filter(entry => entry.tagID != id)
        }

        function select2Override(id, color) {
            //our override function for when anyone selects one of the color boxes

            //first check if it is already in our master list, if so then dont add a dup, remove it!
            var doesExistsAlready = collectionArray.filter(entry => entry.type === id && entry.color === color);
            if (doesExistsAlready.length != 0) {
                for (var i = 0; i < doesExistsAlready.length; i++) {
                    removeFromList(doesExistsAlready[i].tagID);
                }
                
            } else {
                //its not found in our master list 
                //we need to get a unique if to accompy this entry, fund the highest existing value in the current list and add 1
                var lastID = findMaxValue($('#tag')) + 1;

                //push it to our master list
                collectionArray.push({ "type": id, "color": color, "tagID": lastID });

                //get the selected value from our initial list so we can pull out the "simple text" to display
                var check = $.grep(data, function (obj) { return obj.id === id; })[0];

                //decorate our selection with a color depending on what they selected
                var colorDisplay;
                switch(color) {
                  case 0:
                        colorDisplay = "red";
                    break;
                  case 1:
                    colorDisplay =  "orange"
                    break;
                  case 2:
                        colorDisplay =  "green";
                    break;
                }
                //prep our new select option with our new color, simple text and unique id and set to selected
                var newOption = new Option('<div style="color:' + colorDisplay + ';display:inline;">' + check.simpleText + '</div>', lastID, true, true);

                //append it to our list and call our change method so js reacts to the change
                $('#tag').append(newOption).trigger('change');
            }
        }

        //function to find the max value from our already existing list
        function findMaxValue(element) {
            var maxValue = undefined;
            $('option', element).each(function() {
                var val = $(this).attr('value');
                val = parseInt(val, 10);
                if (maxValue === undefined || maxValue < val) {
                    maxValue = val;
                }
            });
            return maxValue;
        }
        .tableSelect {
            min-width: 20px;
            height: 20px;
            border: 1px solid #fff;
            cursor: pointer;
            margin-bottom: 10px;
        }

        .tableSelectGreen {
            background-color: green;
        }

        .tableSelectRed {
            background-color: red;
        }

        .tableSelectOrange {
            background-color: orange;
        }

        blockMe{
            min-height:20px;
            min-width:20px;
        }

        .select2-container--default .select2-results__option[aria-selected=true] {
            display: none;
        }
<link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.9/css/select2.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.9/js/select2.min.js"></script>

<select id="tag" style="width: 50%" multiple="multiple"></select>
Travis Acton
  • 4,292
  • 2
  • 18
  • 30
  • Works great! I have a question, rather than hard coding the values in the asp, can I pull it from the c# side using the databind? Because I'm pulling all the values from a database to populate my dropdown. – Charles Xavier Aug 22 '19 at 21:56
  • 1
    It has been years I've worked in asp.net webforms so I won't try to direct you there. In the related MVC framework we have two options which I imagine are still valid for you. First would be to call an ajax or webservice method connected to your backend to bring your data forawrd then iterate into your js objects. The second would be to just prep it server side and inject on page load. Either way, if you want the flexibility and power of JS, it comes at a cost of auto binding controls at times. I wish I could offer a more precise solution but I've been in MVC world long enought to forget forms – Travis Acton Aug 22 '19 at 22:10
2

As I know, one of the best solutions to you is grouping options. I have implemented it like below, but I am not in CSS, so you should add some classes to change positions and colors (I think it's not hard too much).

Sample classes

public class Role
{
    public Role(string name, int value)
    {
        Name = name;
        Value = value;
        SubRoles = new List<SubRole>();
    }

    public string Name { get; set; }
    public int Value { get; set; }
    public List<SubRole> SubRoles { get; set; }


    public enum SubRoleType
    {
        Red = 1,
        Orange = 2,
        Green = 3
    }
}

public class SubRole
{
    public string Name { get; set; }
    public int Value { get; set; }

    public SubRole(string name, int value)
    {
        Name = name;
        Value = value;
    }
}

Code Behind

    protected void Page_Load(object sender, EventArgs e)
    {
        List<Role> roles = new List<Role>();
        Role accountantRole = new Role("Accountant", 1);
        accountantRole.SubRoles.Add(new SubRole(SubRoleType.Red.ToString(), (int)SubRoleType.Red));
        accountantRole.SubRoles.Add(new SubRole(SubRoleType.Orange.ToString(), (int)SubRoleType.Green));
        accountantRole.SubRoles.Add(new SubRole(SubRoleType.Green.ToString(), (int)SubRoleType.Orange));
        roles.Add(accountantRole);

        Role managmentRole = new Role("Accountant Managment", 2);
        managmentRole.SubRoles.Add(new SubRole(SubRoleType.Red.ToString(), (int)SubRoleType.Red));
        managmentRole.SubRoles.Add(new SubRole(SubRoleType.Orange.ToString(), (int)SubRoleType.Green));
        managmentRole.SubRoles.Add(new SubRole(SubRoleType.Green.ToString(), (int)SubRoleType.Orange));
        roles.Add(managmentRole);

        foreach (var role in roles)
            AddRoleToDropDownList(ddlRole, role);          
    }

    private void AddRoleToDropDownList(DropDownList list, Role role)
    {
        foreach (var subRole in role.SubRoles)
        {
            ListItem item = new ListItem(subRole.Name, subRole.Name);
            item.Attributes["data-category"] = role.Name;
            list.Items.Add(item);
        }
    }    

Markup

<asp:DropDownList runat="server" ID="ddlRole" />

<script>
    var groups = {};
    $("select option[data-category]").each(function () {
        groups[$.trim($(this).attr("data-category"))] = true;
    });
    $.each(groups, function (c) {
        $("select option[data-category='" + c + "']").wrapAll('<optgroup label="' + c + '">');
    });
</script>
Ali Soltani
  • 9,589
  • 5
  • 30
  • 55
-2

yes there are quite some a java and css magic that can help you do this. Have a look at this one from MDB, or from Telerik

There are however not that hard to make, you need

  1. A Div that you populate with your choices, any html tag will do as long as you can click it. make clicking it change the CSS to a specific class and register the click (and unclick in a list/form for post-back later)

  2. Place the div on the item that is supposed to be the drop down and unhide it

  3. Make the pop-up / dopdown go away when clicking on the "OK","Cancel", "escape key", "enter key"

  4. Onhide of the dip post back your choices to process the change

Have a look at jquery, will have samples for each of these i think

Walter Verhoeven
  • 3,867
  • 27
  • 36