From 1a1ba989908721ba5229029a631c104a103e4358 Mon Sep 17 00:00:00 2001 From: Jim Brandt Date: Mon, 18 Nov 2024 16:36:47 -0500 Subject: [PATCH] Convert select menus from bootstrap-select to tom-select Reuse the selectpicker class since tom-select allows you to set the class to find select elements and we already apply the selectpicker class. --- lib/RT/Interface/Web.pm | 2 +- share/html/Elements/Footer | 1 - share/static/css/elevator/main.css | 3 +- share/static/js/util.js | 59 +++++++++++++++++++++++------- 4 files changed, 48 insertions(+), 17 deletions(-) diff --git a/lib/RT/Interface/Web.pm b/lib/RT/Interface/Web.pm index 29c8c8b2757..5598771285d 100644 --- a/lib/RT/Interface/Web.pm +++ b/lib/RT/Interface/Web.pm @@ -130,7 +130,6 @@ sub JSFiles { jquery-ui-patch-datepicker.js selectize.min.js bootstrap.bundle.min.js - bootstrap-select.min.js bootstrap-combobox.js i18n.js util.js @@ -152,6 +151,7 @@ sub JSFiles { jquery.jgrowl.min.js clipboard.min.js pagelayout.js + tom-select.complete.min.js }, RT->Config->Get('JSFiles'); } diff --git a/share/html/Elements/Footer b/share/html/Elements/Footer index cf0924e7788..f9ef6aacac1 100644 --- a/share/html/Elements/Footer +++ b/share/html/Elements/Footer @@ -79,7 +79,6 @@ % } diff --git a/share/static/css/elevator/main.css b/share/static/css/elevator/main.css index aa61b50e23c..1637c18a48b 100644 --- a/share/static/css/elevator/main.css +++ b/share/static/css/elevator/main.css @@ -3,11 +3,12 @@ @import "tablesorter.css"; @import "farbtastic.css"; @import "bootstrap.css"; -@import "bootstrap-select.css"; @import "bootstrap-combobox.css"; @import "dropzone.css"; @import "dropzone.customized.css"; @import "selectize.default.css"; +@import "tom-select.bootstrap5.css"; +@import "tom-select-rt.css"; @import "base.css"; @import "collection.css"; diff --git a/share/static/js/util.js b/share/static/js/util.js index 71d6827085a..95974c7e008 100644 --- a/share/static/js/util.js +++ b/share/static/js/util.js @@ -356,6 +356,41 @@ function textToHTML(value) { .replace(/\n/g, "\n
"); }; +// Initialize the tom-select library +function initializeSelectElements(elt) { + + // The selectpicker class was used by the bootstrap-select + // JS library as the default. We retained it because tom-select + // allows you to set any class value and all of the RT dropdowns + // already had 'selectpicker'. + + elt.querySelectorAll('select.selectpicker').forEach((elt)=>{ + + /* We shouldn't initialize on parts of the page that have already + been initialized, so this check shouldn't be needed. Leave commented + out for now pending more testing. + + if ( elt.tomselect ) { + // This can get called on elements already initialized. + return; + } +*/ + + let settings = { + allowEmptyOption: true, + }; + + if ( elt.options && elt.options.length < RT.Config.SelectLiveSearchLimit ) { + // Under the config limit, don't show the search input box, + // just a regular dropdown. + settings.controlInput = null; + } + + new TomSelect(elt,settings); + + }); +} + function ReplaceAllTextareas(elt) { window.CKEDITOR ||= { "instances": {} }; @@ -624,7 +659,7 @@ function refreshCollectionListRow(tr, table, success, error) { // Get the new replaced tr tr = table.find('tr[data-index=' + index + ']'); initDatePicker(tr); - tr.find('.selectpicker').selectpicker(); +// tr.find('.selectpicker').selectpicker(); RT.Autocomplete.bind(tr); if (success) { success(response) } }, @@ -775,7 +810,7 @@ jQuery(function() { elt.classList.remove('hasDatepicker'); }); - jQuery(evt.detail.historyElt).find('.selectpicker').selectpicker('destroy').addClass('selectpicker'); +// jQuery(evt.detail.historyElt).find('.selectpicker').selectpicker('destroy').addClass('selectpicker'); }); document.body.addEventListener('actionsChanged', function(evt) { @@ -846,6 +881,7 @@ jQuery(function() { }); htmx.onLoad(function(elt) { + initializeSelectElements(elt); ReplaceAllTextareas(elt); AddAttachmentWarning(); jQuery(elt).find('a.delete-attach').click( function() { @@ -979,7 +1015,7 @@ htmx.onLoad(function(elt) { var new_operator = form.find(':input[name="' + val + 'Op"]:first').clone(); row.children('div.operator').children().remove(); row.children('div.operator').append(new_operator); - row.children('div.operator').find('select.selectpicker').selectpicker(); +// row.children('div.operator').find('select.selectpicker').selectpicker(); var new_value = form.find(':input[name="ValueOf' + val + '"]:first'); if ( new_value.hasClass('ui-autocomplete-input') ) { @@ -994,7 +1030,7 @@ htmx.onLoad(function(elt) { new_value.attr('id', null); row.children('div.value').children().remove(); row.children('div.value').append(new_value); - row.children('div.value').find('select.selectpicker').selectpicker(); +// row.children('div.value').find('select.selectpicker').selectpicker(); if ( new_value.hasClass('datepicker') ) { new_value.removeClass('hasDatepicker'); initDatePicker(row); @@ -1443,7 +1479,7 @@ jQuery(function () { editor.find(':input:visible:enabled:first').focus(); setTimeout( function(){ - editor.find('.selectpicker').selectpicker('toggle'); + editor.find('select.selectpicker')[0].tomselect.open(); }, 100); jQuery('body').addClass('inline-editing'); @@ -1788,17 +1824,12 @@ htmx.onLoad( function() { RT.UserMessages = {}; } ); -function updateSelectpickerLiveSearch (element) { - element ||= jQuery('.selectpicker'); - element.filter(':not([data-live-search])').each(function() { - jQuery(this).attr('data-live-search', jQuery(this).find('option').length >= RT.Config.SelectLiveSearchLimit ? true : false ); - }); -} - function refreshSelectpicker (element) { element ||= jQuery('.selectpicker'); - updateSelectpickerLiveSearch(element); - element.selectpicker('refresh'); + // Why would we call this on non-selectpicker elements? + if ( element.tomselect ) { + element.tomselect.refreshOptions(); + } } function checkRefreshState(elt) {