Files
PyRIGS/pipeline/source_assets/js/autocompleter.js
Joe Banks a29384a314 Bootstrap 5 & remove jQuery (#661)
* build: move frontend assets to Bootstrap 5.3 and Tom Select

* feat: migrate templates and scripts to Bootstrap 5, dropping jQuery

Templates
- Rename Bootstrap 4 classes and data attributes (data-bs-*, ms-/me-/ps-/pe-,
  text-start/end, form-group -> mb-3, input-group wrappers removed, badges,
  custom controls -> form-check/form-select, media -> d-flex, btn-close, ...)
- Dark mode now uses Bootstrap 5.3 colour modes (data-bs-theme on <html>),
  set from <head> to avoid a flash of the light theme
- Remove the Bootstrap 4 /bootstrap/ demo page
- Contextual table classes (table-success etc.) still work as plain
  backgrounds for the rigboard key

JavaScript
- Remove jQuery: src.js/interaction.js and every inline template script are
  rewritten in plain JS on top of small helpers (helpers.js, bundled with
  Bootstrap into jpop.js so inline scripts can use them)
- Replace bootstrap-select/ajax-bootstrap-select with Tom Select. The remote
  search now sends the `q` parameter and reads `text`, which is what the
  API actually uses (the old picker sent `term` and read `label`)
- HTML fragments are fetched with an X-Requested-With header so views keep
  rendering their modal variants, and fetched scripts are re-executed
- Remove the Bootstrap 4 plugin script includes

* fix: stop rigboard header buttons stretching to full width under Bootstrap 5

* fix: restore dark-mode brand colours and contextual table colours under Bootstrap 5

* fix: brighten dark-mode links and make coloured card outlines visible

* fix: calendar layout and grid borders, navbar search button width, guard missing modal links

* fix: readable text in dark-mode Tom Select inputs and placeholders, restore form-control column widths

* build: split the Dockerfile into cached stages with prod and dev targets

* deploy: make compose.yml development-only with live reload, and document it

* fix: keep badge text colour for links inside badges

* fix: restore table-bordered grid lines in dark mode

* fix: include Bootstrap's progress styles and keep progress labels readable
2026-10-02 16:28:11 +01:00

139 lines
4.7 KiB
JavaScript

// Select widgets, built on Tom Select.
// * <select class="ts-select" data-sourceurl="..."> loads its options remotely as the user types
// * <select class="ts-select"> without a source URL is a plain (optionally multiple) select
var clearSelectionLabel = '(no selection)';
function changeSelectedValue(select, pk, text, update_url) { // Pass in the <select> and the new option's parameters
var ts = select.tomselect;
if (!ts) {
return;
}
ts.clear(true);
ts.clearOptions();
ts.addOption({value: pk, text: text, update_url: update_url || ''});
ts.setValue(pk, true);
select.dispatchEvent(new Event('change', {bubbles: true})); // Trigger the change function manually
}
function selectedUpdateUrl(select) {
var ts = select.tomselect;
var value = ts.getValue();
if (!value) {
return '';
}
var option = ts.options[value];
if (option && option.update_url !== undefined) {
return option.update_url;
}
var domOption = select.querySelector('option[value="' + CSS.escape(value) + '"]');
return domOption && domOption.dataset.update_url ? domOption.dataset.update_url : null;
}
function refreshUpdateHref(select) {
var target = document.getElementById(select.id + '-update');
if (!target) {
return;
}
var update_url = selectedUpdateUrl(select);
if (update_url === '') { // Nothing is selected
target.removeAttribute('href');
target.classList.add('disabled');
} else if (update_url !== null) {
target.setAttribute('href', update_url);
target.classList.remove('disabled');
}
}
function initPicker(select) {
var noClear = 'noclear' in select.dataset;
var multiple = select.multiple;
var url = select.dataset.sourceurl;
var plugins = [];
if (multiple) {
plugins.push('remove_button');
} else if (!noClear) {
plugins.push('clear_button');
}
var settings = {
plugins: plugins,
valueField: 'value',
labelField: 'text',
searchField: ['text'],
allowEmptyOption: false,
placeholder: select.dataset.noneSelectedText || '',
maxOptions: null,
hideSelected: false,
closeAfterSelect: !multiple
};
if (url) {
Object.assign(settings, {
// Results come from the server so don't filter them locally
score: function () { return function () { return 1; }; },
shouldLoad: function (query) { return query.length > 0; },
load: function (query, callback) {
var separator = url.indexOf('?') === -1 ? '?' : '&';
fetch(url + separator + 'q=' + encodeURIComponent(query), {credentials: 'same-origin'})
.then(function (response) { return response.json(); })
.then(function (data) {
callback(data.map(function (item) {
return {value: item.pk, text: item.text, update_url: item.update};
}));
})
.catch(function () { callback(); });
}
});
}
var update_urls = {};
select.querySelectorAll('option[data-update_url]').forEach(function (option) {
update_urls[option.value] = option.dataset.update_url;
});
var ts = new TomSelect(select, settings);
Object.keys(update_urls).forEach(function (value) {
if (ts.options[value]) {
ts.options[value].update_url = update_urls[value];
}
});
if (select.id && document.getElementById(select.id + '-update')) {
select.addEventListener('change', function () { // on change, update the edit button href
refreshUpdateHref(select);
});
refreshUpdateHref(select); // Ensure href is correct at the beginning
}
}
function initSelects(root) {
if (typeof TomSelect === 'undefined') {
return;
}
(root || document).querySelectorAll('select.ts-select').forEach(function (select) {
if (!select.tomselect) {
initPicker(select);
}
});
}
ready(function () {
initSelects();
//When update/edit modal box submitted
var modal = document.getElementById('modal');
if (modal) {
modal.addEventListener('hide.bs.modal', function () {
if (typeof modaltarget !== 'undefined' && modaltarget && typeof modalobject !== 'undefined' && modalobject !== "") {
//Update the selector with new values
var select = document.querySelector(modaltarget);
if (select) {
changeSelectedValue(select, modalobject[0]['pk'], modalobject[0]['fields']['name'], modalobject[0]['update_url']);
}
}
});
}
});