Last active
April 10, 2020 16:18
-
-
Save mdchaney/912b62dc29322f376f1c56e1bf0dfcbb to your computer and use it in GitHub Desktop.
Bootstrap 4 multi-select replacement
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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