Skip to content

Instantly share code, notes, and snippets.

@mdchaney
Last active April 10, 2020 16:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mdchaney/912b62dc29322f376f1c56e1bf0dfcbb to your computer and use it in GitHub Desktop.
Save mdchaney/912b62dc29322f376f1c56e1bf0dfcbb to your computer and use it in GitHub Desktop.
Bootstrap 4 multi-select replacement
This code will replace <select multiple...> boxes with a readonly text input that shows all items selected. When clicked, a modal will appear giving the user the ability to select items with check boxes. In the page load function, just call setup_selection_popups(). The select elements should have a class of "replaceable", and of course the multiple attribute must be present. Everything is contained in one javascript file, and a css file. Bootstrap 4 uses jquery, so it must be present. However, jquery is used only to show the modal.
/* select popup box and lists */
div.multi-select-popup {
width: 100%;
overflow: auto;
color: black;
background: white;
text-align: left;
padding: 5px;
z-index: 100;
}
div.multi-select-popup div.portal {
width: 100%;
overflow: auto;
}
table.scroller td {
padding: 0;
vertical-align: top;
}
div.multi-select-popup ul {
float: left;
margin: 0; /*removes indent IE and Opera*/
padding: 0; /*removes indent Mozilla and NN7*/
list-style-type: none; /*turns off display of bullet*/
}
div.multi-select-popup ul li {
text-align: left;
white-space: nowrap;
padding: 1px 3px;
list-style-type: none; /*turns off display of bullet*/
}
div.multi-select-popup ul li:nth-of-type(odd) {
background: #fdd;
}
a.edit-popup {
color: #ee1c25;
}
/* horizontal scrollers on admin page */
div.portal {
width: 800px;
overflow: auto;
}
div.portal table.scroller {
margin: 0;
}
div.portal table.scroller tr td {
white-space: nowrap;
}
div.portal table.scroller ul li label {
margin: 0;
}
// make replacement fields that are readonly look normal
input[type=text][readonly].form-control.replacement {
background-color: inherit;
}
function setup_selection_popups() {
document.querySelectorAll('select.replaceable[multiple]').forEach(function(field) {
field.style.display = 'none';
let repl = document.createElement('input');
repl.readOnly = true;
repl.type = 'text';
repl.placeholder = 'Click to select items';
repl.className = 'form-control replacement';
repl.id = field.id + '_replacement';
repl.style.cursor = 'pointer';
repl.onclick = function() {
let header = field.parentElement.querySelector('label').textContent;
selection_popup(field, header);
}
field.parentElement.insertAdjacentElement('beforeend', repl);
show_selected_items(field);
});
function show_selected_items(field) {
let repl = document.getElementById(field.id + '_replacement');
repl.value = Array.from(field.selectedOptions).map(function(opt) { return opt.text }).join(', ');
}
function selection_popup(field, header, items_per_col) {
if (!items_per_col) items_per_col=15;
var limit = field.dataset.limit;
limit = (limit ? parseInt(limit) : 999);
var body = document.getElementsByTagName('body')[0];
var POPUP = field.type=='select-multiple' ? 'multi-select-popup' : 'one-select-popup'
var INPUT = field.type=='select-multiple' ? 'checkbox' : 'radio'
var all_uls=[];
for (var i=0 ; i<Math.ceil(field.options.length/items_per_col) ; i++) {
var ul = document.createElement('ul');
for (var j=0 ; j<items_per_col ; j++) {
var this_option = field.options[i*items_per_col+j];
if (!this_option) break;
var checkbox_name;
if (INPUT == 'radio') {
checkbox_name = field.id+'_radio';
} else {
checkbox_name = field.id+'_checkbox_'+(i*items_per_col+j);
}
var checkbox = document.createElement('input');
checkbox.type = INPUT;
checkbox.value = this_option.value;
checkbox.checked = this_option.selected;
checkbox.onchange = enforce_limit;
var li = document.createElement('li');
var label = document.createElement('label');
var label_text = document.createTextNode(' ' + this_option.text);
label.appendChild(checkbox);
label.appendChild(label_text);
li.appendChild(label);
ul.appendChild(li);
}
all_uls.push(ul);
}
var table = document.createElement('table');
table.className = 'scroller';
var tr = document.createElement('tr');
for (ul of all_uls) {
var td = document.createElement('td');
td.appendChild(ul);
tr.appendChild(td);
}
table.appendChild(tr);
var apply_button = document.createElement('button');
apply_button.className = 'btn btn-primary btn-sm mt-2 mr-2 apply-btn';
apply_button.appendChild(document.createTextNode('Save'));
var clear_button = document.createElement('button');
clear_button.className = 'btn btn-danger btn-sm mt-2 mr-2 clear-btn';
clear_button.appendChild(document.createTextNode('Clear'));
var cancel_button = document.createElement('button');
cancel_button.className = 'btn btn-secondary btn-sm mt-2 mr-2 cancel-btn';
cancel_button.appendChild(document.createTextNode('Cancel'));
apply_button.onclick = apply_selection_popup;
clear_button.onclick = clear_selection_popup;
cancel_button.onclick = cancel_selection_popup;
var div = document.createElement('div');
div.className = POPUP;
var portal = document.createElement('div');
portal.className = 'portal';
portal.appendChild(table);
div.appendChild(portal);
let modal = document.createElement('div');
modal.className = 'modal fade';
modal.id = 'multiselect-modal';
modal.tabindex = -1;
modal.role = 'dialog';
modal.setAttribute('aria-hidden', true);
modal.setAttribute('aria-labelledby', 'modal-label');
let modal_dialog = document.createElement('div');
modal_dialog.className = 'modal-dialog modal-lg';
modal_dialog.role = 'document';
let modal_content = document.createElement('div');
modal_content.className = 'modal-content';
let modal_header = document.createElement('div');
modal_header.className = 'modal-header';
let h5 = document.createElement('h5');
h5.className = 'modal-title';
h5.id = 'modal-label';
h5.appendChild(document.createTextNode(header));
let modal_body = document.createElement('div');
modal_body.className = 'modal-body';
let modal_footer = document.createElement('div');
modal_footer.className = 'modal-footer';
modal_footer.appendChild(apply_button);
modal_footer.appendChild(clear_button);
modal_footer.appendChild(cancel_button);
modal_body.appendChild(div);
modal_header.appendChild(h5);
modal_content.appendChild(modal_header);
modal_content.appendChild(modal_body);
modal_content.appendChild(modal_footer);
modal_dialog.appendChild(modal_content);
modal.appendChild(modal_dialog);
body.appendChild(modal);
jQuery('#multiselect-modal').modal();
jQuery('#multiselect-modal').on('hidden.bs.modal', function(e) {
body.removeChild(modal);
});
function apply_selection_popup() {
var cbs = document.querySelectorAll('.portal input:checked');
field.querySelectorAll('option:checked').forEach(function(o) {
o.selected = false;
});
for (var opt_val of cbs) {
field.querySelector('option[value="' + opt_val.value + '"]').selected = true
}
cancel_selection_popup();
show_selected_items(field);
}
function cancel_selection_popup() {
jQuery('#multiselect-modal').modal('hide');
}
function clear_selection_popup() {
document.querySelectorAll('.portal input:checked').forEach(function(check) {
check.checked = false;
});
}
function enforce_limit(e) {
if (this.checked) {
const num_checked = document.querySelectorAll('.portal input:checked').length;
if (num_checked > limit) {
alert("You may select no more than " + limit + " items");
this.checked = false;
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment