mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-10-10 14:52:53 +00:00
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
This commit is contained in:
@@ -1,101 +1,138 @@
|
||||
function changeSelectedValue(obj,pk,text,update_url) { //Pass in JQuery object and new parameters
|
||||
//console.log('Changing selected value');
|
||||
obj.find('option').remove(); //Remove all the available options
|
||||
obj[0].add(new Option(text, pk, true, true)); // Add new option
|
||||
//obj.selectpicker('val', pk); //Set the new value to be selected
|
||||
obj.selectpicker('refresh');
|
||||
obj.change(); //Trigger the change function manually
|
||||
//console.log(obj);
|
||||
// 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 refreshUpdateHref(obj) {
|
||||
//console.log('Refreshing Update URL');
|
||||
targetObject = $('#'+obj.attr('id')+'-update');
|
||||
update_url = $('option:selected', obj).data('update_url');
|
||||
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;
|
||||
}
|
||||
|
||||
if (update_url=="") { //Probably "clear selection" has been chosen
|
||||
//console.log('Trying to disable');
|
||||
targetObject.removeAttr('href');
|
||||
targetObject.addClass('disabled');
|
||||
} else {
|
||||
targetObject.prop('href', update_url);
|
||||
targetObject.removeClass('disabled');
|
||||
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(obj) {
|
||||
var options = {
|
||||
ajax: {
|
||||
url: obj.data('sourceurl'),
|
||||
type: 'GET',
|
||||
dataType: 'json',
|
||||
// Use "{{{q}}}" as a placeholder and Ajax Bootstrap Select will
|
||||
// automatically replace it with the value of the search query.
|
||||
data: {
|
||||
term: '{{{q}}}'
|
||||
}
|
||||
},
|
||||
locale: {
|
||||
emptyTitle: ''
|
||||
},
|
||||
clearOnEmpty:false,
|
||||
//log: 3,
|
||||
preprocessData: function (data) {
|
||||
var i, l = data.length, array = [];
|
||||
if (!obj.data('noclear')) {
|
||||
array.push({
|
||||
text: clearSelectionLabel,
|
||||
value: '',
|
||||
data:{
|
||||
update_url: '',
|
||||
subtext:''
|
||||
}
|
||||
});
|
||||
}
|
||||
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');
|
||||
}
|
||||
|
||||
if (l) {
|
||||
for(i = 0; i < l; i++){
|
||||
array.push($.extend(true, data[i], {
|
||||
text: data[i]['label'],
|
||||
value: data[i]['pk'],
|
||||
data:{
|
||||
update_url: data[i]['update'],
|
||||
subtext:''
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
||||
var settings = {
|
||||
plugins: plugins,
|
||||
valueField: 'value',
|
||||
labelField: 'text',
|
||||
searchField: ['text'],
|
||||
allowEmptyOption: false,
|
||||
placeholder: select.dataset.noneSelectedText || '',
|
||||
maxOptions: null,
|
||||
hideSelected: false,
|
||||
closeAfterSelect: !multiple
|
||||
};
|
||||
//console.log(obj.data);
|
||||
if (!obj.data('noclear')) {
|
||||
obj.prepend($("<option></option>")
|
||||
.attr("value",'')
|
||||
.text(clearSelectionLabel)
|
||||
.data('update_url','')); //Add "clear selection" option
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
obj.selectpicker().ajaxSelectPicker(options); //Initiaise selectPicker
|
||||
obj.change(function(){ //on change, update the edit button href
|
||||
//console.log('Selectbox Changed');
|
||||
refreshUpdateHref(obj);
|
||||
});
|
||||
|
||||
refreshUpdateHref(obj); //Ensure href is correct at the beginning
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
clearSelectionLabel = '(no selection)';
|
||||
|
||||
$(".selectpicker").each(function(){initPicker($(this))});
|
||||
|
||||
//When update/edit modal box submitted
|
||||
$('#modal').on('hide.bs.modal', function (e) {
|
||||
if (modaltarget != undefined && modalobject != "") {
|
||||
//Update the selector with new values
|
||||
changeSelectedValue($(modaltarget),modalobject[0]['pk'],modalobject[0]['fields']['name'],modalobject[0]['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']);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user