Solution:
Click Twice
The first click on the cell initialises select2, the second click then tells select2 to display the dropdown. To only require a single click you can call the open method described at https://select2.org/programmatic-control/methods after select2 is initialized.
// Initialise select2
let selectEle = cellEle.children("select").select2();
// Open the select2 dropdown
selectEle.select2('open');
Replace Select
When clicking on a value select2 is being destroyed, the .html()
call should replace the select with the selected value, however it does not function correctly as the id
property doesn’t exist on the stored value, this is causing the cell to revert to a normal select.
The code which handles the "select2:close"
event contains the line selectedValue = selectEle.select2('data')[0]['text'];
which places the text of a just the first selected value [0]
into the variable selectedValue
. After this the HTML of the cell is updated using cellEle.html(selectedValue.id);
, but at this point selectedValue
contains just the text (e.g. “Alabama”) so there is no .id
property. To resolve this both the ID and Text could be stored in the array, then used where needed, for example:
// Store the id and text of all selected values in the array
selectedValue = selectEle.select2('data').map(function(value) {
return { id: value.id, text: value.text };
});
// Get an array of IDs for the selected values (for preselecting values when select2 loads)
selectEle.val(states[cellId].map(function(value) { return value.id })).trigger('change');
// Get a comma separated string of the selected values (for populating the text in the cell)
cellEle.html(states[cellId].map(function(value) { return value.text; }).join(','));
Multi Select Mode – An example which allows multiple selections is https://jsfiddle.net/aqgbxz1d/ and also incorporated into this answer below. This seems to be the desired mode based on the multiple="multiple"
property in the question.
The example is updated to no longer use the select2:close
event. Instead it uses the change
event to store value changes, and a second click
event handler on the document to destroy the select2 dropdown(s) when the user clicks somewhere else on the page. This seems like a better approach given what you are trying to achieve as it leaves the select open for multiple values to be selected.
Single Select Mode – From the comments it seems single select mode may be required. An example which allows just a single selection is https://jsfiddle.net/9jcnwbt2/1/. If single select mode is required, then you would need to:
- Remove the multiple attribute
multiple="multiple"
- add a blank option
<option></option>
- You could also replicate replicate the code from the document click event which destroys select2 and updates the HTML into the change event.
(function ($) {
var states = [];
$(document).ready(function () {
$(".volunteer").on("click", function (e) {
// Check if select is already loaded
if (!$(this).has("select").length) {
var cellEle = $(this);
var cellId = this.id;
// Populate select element
cellEle.html(`<select class="js-example-basic" multiple="multiple">
<option value="AL">Alabama</option>
<option value="WY">Wyoming</option>
</select>`);
// Initialise select2
let selectEle = cellEle.children("select").select2({placeholder: "Select a value"});
// Open the select dropdown so user doesn't have to click twice
selectEle.select2('open');
// Check if there is an existing value for this cell
if (states[cellId]) {
// preselect existing value
selectEle.val(states[cellId].map(function (value) {
return value.id
})).trigger('change');
}
// Attach event handler to store value changes
selectEle.on('change', function (e) {
// Get selected values
selectedValues = $(this).select2('data');
// Update the states array with id and text of selected
// values. The id is needed to restore the values if select2
// is reloaded on this cell. The text is required to generate
// the replacement text shown in the cell
states[cellId] = selectedValues.map(function (value) {
return {
id: value.id,
text: value.text
};
});
});
}
// Don't propagate the event
// This prevents this document click handler from executing which would
// remove select2 by calling destroy
e.stopPropagation();
});
});
// Attach event handler to document to capture any clicks
// This will be triggered for all clicks on the page, except those
// captured by the method described above this prevents this firing
// with e.stopPropagation()
// Which this is called select2 on any cells must be destoryed and their
// text value populated
$(document).on('click', function (e) {
// Loop through all select2 elements
$('.js-example-basic').each(function (idx) {
// Get the ID of the cell that's been selected
let cellId = $(this).parent().attr('id');
// Destroy select2 on this element
$(this).select2('destroy');
// Change html on the parent element (td) to just show the value
if(states[cellId] && states[cellId].length > 0){
$(this).parent().html(states[cellId].map(function (value) {
return value.text;
}).join(','));
} else {
$(this).parent().html("Select a value...")
}
});
});
})(jQuery)
.js-example-basic {
width: 200px;
}
thead{
font-weight: bold;
}
table, th, td {
border: 1px solid black;
}
tr {
height: 36px;
}
td {
width: 200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
<table>
<thead>
<tr>
<td>Table Header 1</td>
<td>Table Header 2</td>
</tr>
</thead>
<tbody>
<tr>
<td class="volunteer" id="47">Select a value...</td>
<td class=""></td>
</tr>
<tr>
<td class="volunteer" id="48">Select a value...</td>
<td class=""></td>
</tr>
</tbody>
</table>