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>