Replace jQuery with VanillaJS

This commit is contained in:
Michael Weimann 2022-11-29 21:47:26 +01:00
parent 7bbdb95885
commit 282f4b45ac
No known key found for this signature in database
GPG Key ID: 34F0524D4DA694A1
11 changed files with 223 additions and 129 deletions

View File

@ -3,6 +3,7 @@
"extends": [ "plugin:editorconfig/all" ],
"plugins": [ "editorconfig" ],
"rules": {
"prefer-arrow-callback": "error",
"no-var": "error",
"quotes": [
"error",

View File

@ -38,13 +38,13 @@ function form_spinner($name, $label, $value)
</button>
</div>
<script type="text/javascript">
$(\'#spinner-' . $name . '-down\').click(function () {
const spinner = $(\'#spinner-' . $name . '\');
spinner.val(parseInt(spinner.val()) - 1);
document.getElementById("spinner-' . $name . '-down")?.addEventListener("click", () => {
const spinner = document.getElementById("spinner-' . $name . '");
spinner.value = parseInt(spinner.value, 10) - 1;
});
$(\'#spinner-' . $name . '-up\').click(function () {
const spinner = $(\'#spinner-' . $name . '\');
spinner.val(parseInt(spinner.val()) + 1);
document.getElementById("spinner-' . $name . '-up")?.addEventListener("click", () => {
const spinner = document.getElementById("spinner-' . $name . '");
spinner.value = parseInt(spinner.value, 10) + 1;
});
</script>
', 'spinner-' . $name);

View File

@ -389,15 +389,18 @@ function render_table($columns, $rows, $data = true)
* @param string $href
* @param string $label
* @param string $class
* @param string $id
* @return string
*/
function button($href, $label, $class = '')
function button($href, $label, $class = '', $id = '')
{
if (!Str::contains(str_replace(['btn-sm', 'btn-xl'], '', $class), 'btn-')) {
$class = 'btn-secondary' . ($class ? ' ' . $class : '');
}
return '<a href="' . $href . '" class="btn ' . $class . '">' . $label . '</a>';
$idAttribute = $id ? 'id="' . $id . '"' : '';
return '<a ' . $idAttribute . ' href="' . $href . '" class="btn ' . $class . '">' . $label . '</a>';
}
/**

View File

@ -41,24 +41,16 @@ function public_dashboard_view($stats, $free_shifts)
stats(__('Angels needed for nightshifts'), $stats['needed-night']),
stats(__('Angels currently working'), $stats['angels-working'], 'default'),
stats(__('Hours to be worked'), $stats['hours-to-work'], 'default'),
'<script>
$(function () {
setInterval(function () {
$(\'#content .wrapper\').load(window.location.href + \' #public-dashboard\');
}, 60000);
})
</script>'
], 'statistics'),
$needed_angels
], 'public-dashboard'),
]),
div('first col-md-12 text-center', [buttons([
button_js(
'
$(\'#navbar-collapse-1,.navbar-nav,.navbar-toggler,#footer,#fullscreen-button\').remove();
$(\'.navbar-brand\').append(\' ' . __('Public Dashboard') . '\');
',
icon('fullscreen') . __('Fullscreen')
button(
'#',
icon('fullscreen') . __('Fullscreen'),
'',
'dashboard-fullscreen'
),
auth()->user() ? button(
public_dashboard_link($isFiltered ? [] : ['filtered' => 1] + $filter),

View File

@ -53,20 +53,20 @@ function UserDriverLicense_edit_view($user_source, $user_driver_license)
]),
'
<script type="text/javascript">
$(function () {
const checkbox = $(\'#wants_to_drive\');
if (checkbox.is(\':checked\'))
$(\'#driving_license\').show();
else
$(\'#driving_license\').hide();
const drivingLicenseElement = document.getElementById("driving_license");
checkbox.click(function () {
if ($(\'#wants_to_drive\').is(\':checked\'))
$(\'#driving_license\').show();
else
$(\'#driving_license\').hide();
});
if (drivingLicenseElement) {
const checkbox = document.getElementById("wants_to_drive");
drivingLicenseElement.style.display = checkbox?.checked
? ""
: "none";
checkbox.addEventListener("click", () => {
drivingLicenseElement.style.display = document.getElementById("wants_to_drive")?.checked
? ""
: "none";
});
}
</script>
'
], true);

View File

@ -69,14 +69,13 @@ function formatFromNow(timestamp) {
/**
* Initialises all countdown fields on the page.
*/
ready(function () {
$.each($('[data-countdown-ts]'), function (i, e) {
const span = $(e);
const timestamp = span.data('countdown-ts');
const text = span.html();
span.html(text.replace('%c', formatFromNow(timestamp)));
setInterval(function () {
span.html(text.replace('%c', formatFromNow(timestamp)));
ready(() => {
document.querySelectorAll('[data-countdown-ts]').forEach((element) => {
const timestamp = element.dataset.countdownTs;
const template = element.innerHTML;
element.innerHTML = template.replace('%c', formatFromNow(timestamp));
setInterval(() => {
element.innerHTML = template.replace('%c', formatFromNow(timestamp));
}, 1000);
});
});

View File

@ -0,0 +1,37 @@
import { ready } from './ready';
ready(() => {
if (!document.getElementById('public-dashboard')) return;
// reload page every minute
setInterval(async () => {
const response = await fetch(window.location.href);
if (!response.ok) {
console.warn('error loading dashboard');
return;
}
const responseData = await response.text();
const parser = new DOMParser();
const dummyDocument = parser.parseFromString(responseData, 'text/html');
const dashboardContent = dummyDocument.getElementById('public-dashboard');
document.querySelector('#content .wrapper').innerHTML = dashboardContent.outerHTML;
}, 60000);
// Handle fullscreen button
// - Remove some elements from UI
// - Add "Public Dashboard" to title
document.getElementById('dashboard-fullscreen')
?.addEventListener('click', (event) => {
event.preventDefault();
document.querySelectorAll(
'#navbar-collapse-1,.navbar-nav,.navbar-toggler,#footer,#fullscreen-button'
).forEach((element) => {
element.parentNode.removeChild(element);
});
document.querySelector('.navbar-brand')
?.appendChild(document.createTextNode('Dashboard'));
});
});

View File

@ -1,7 +1,15 @@
require('select2');
import 'select2';
import { formatDay, formatTime } from './date';
import { ready } from './ready';
/**
* @param {HTMLElement} element
*/
const triggerChange = (element) => {
const changeEvent = new Event('change');
element.dispatchEvent(changeEvent);
}
/**
* Sets all checkboxes to the wanted state
*
@ -9,8 +17,8 @@ import { ready } from './ready';
* @param {boolean} checked True if the checkboxes should be checked
*/
global.checkAll = (id, checked) => {
$('#' + id + ' input[type="checkbox"]').each(function () {
this.checked = checked;
document.querySelectorAll('#' + id + ' input[type="checkbox"]').forEach((element) => {
element.checked = checked;
});
};
@ -18,11 +26,12 @@ global.checkAll = (id, checked) => {
* Sets the checkboxes according to the given type
*
* @param {string} id The elements ID
* @param {list} shiftsList A list of numbers
* @param {int[]} shiftsList A list of numbers
*/
global.checkOwnTypes = (id, shiftsList) => {
$('#' + id + ' input[type="checkbox"]').each(function () {
this.checked = $.inArray(parseInt(this.value), shiftsList) != -1;
document.querySelectorAll('#' + id + ' input[type="checkbox"]').forEach((element) => {
const value = parseInt(element.value, 10);
element.checked = shiftsList.includes(value);
});
};
@ -37,21 +46,23 @@ global.checkOwnTypes = (id, shiftsList) => {
* @param {Date} to
*/
global.setInput = (from, to) => {
const fromDay = $('#start_day');
const fromTime = $('#start_time');
const toDay = $('#end_day');
const toTime = $('#end_time');
const fromDay = document.getElementById('start_day');
const fromTime = document.getElementById('start_time');
const toDay = document.getElementById('end_day');
const toTime = document.getElementById('end_time');
if (!fromDay || !fromTime || !toDay || !toTime) {
console.warn('cannot set input date because of missing field');
return;
}
fromDay.val(formatDay(from)).trigger('change');
fromTime.val(formatTime(from));
fromDay.value = formatDay(from);
triggerChange(fromDay);
fromTime.value = formatTime(from);
toDay.val(formatDay(to)).trigger('change');
toTime.val(formatTime(to));
toDay.value = formatDay(to);
triggerChange(toDay);
toTime.value = formatTime(to);
};
global.setDay = (days) => {
@ -86,33 +97,42 @@ global.setHours = (hours) => {
setInput(from, to);
};
ready(function () {
ready(() => {
/**
* Disable every submit button after clicking (to prevent double-clicking)
*/
$('form').submit(function (ev) {
$('input[type="submit"]').prop('readonly', true).addClass('disabled');
return true;
document.querySelectorAll('form').forEach((formElement) => {
formElement.addEventListener('submit', () => {
document.querySelectorAll('input[type="submit"],button[type="submit"]').forEach((element) => {
element.readOnly = true;
element.classList.add('disabled');
});
});
});
});
/*
* Button to set current time in time input fields.
*/
ready(function () {
$('.input-group.time').each(function () {
const elem = $(this);
elem.find('button').on('click', function () {
ready(() => {
document.querySelectorAll('.input-group.time').forEach((element) => {
const button = element.querySelector('button');
if (!button) return;
button.addEventListener('click', () => {
const now = new Date();
const input = elem.children('input').first();
input.val(formatTime(now));
const daySelector = $('#' + input.attr('id').replace('time', 'day'));
const days = daySelector.children('option');
const input = element.querySelector('input');
if (!input) return;
input.value = formatTime(now);
const daySelector = document.getElementById(input.id.replace('time', 'day'));
if (!daySelector) return;
const dayElements = daySelector.querySelectorAll('option');
const yyyyMMDD = formatDay(now);
days.each(function (i) {
if ($(days[i]).val() === yyyyMMDD) {
daySelector.val($(days[i]).val());
dayElements.forEach((dayElement) => {
if (dayElement.value === yyyyMMDD) {
daySelector.value = dayElement.value;
return false;
}
});
@ -120,7 +140,7 @@ ready(function () {
});
});
ready(function () {
ready(() => {
$('select').select2({
theme: 'bootstrap-5',
width: '100%',
@ -130,15 +150,17 @@ ready(function () {
/**
* Show oauth buttons on welcome title click
*/
ready(function () {
$('#welcome-title').on('click', function () {
$('.btn-group.btn-group .btn.d-none').removeClass('d-none');
ready(() => {
[
['welcome-title', '.btn-group.btn-group .btn.d-none'],
['settings-title', '.user-settings .nav-item'],
['oauth-settings-title', 'table tr.d-none'],
].forEach(([id, selector]) => {
document.getElementById(id)?.addEventListener('click', () => {
document.querySelectorAll(selector).forEach((element) => {
element.classList.remove('d-none');
});
$('#settings-title').on('click', function () {
$('.user-settings .nav-item').removeClass('d-none');
});
$('#oauth-settings-title').on('click', function () {
$('table tr.d-none').removeClass('d-none');
});
});
@ -149,7 +171,7 @@ ready(function () {
*/
ready(() => {
const filter = document.getElementById('collapseShiftsFilterSelect');
if (!filter || localStorage.getItem('collapseShiftsFilterSelect') !== 'hidden') {
if (!filter || localStorage.getItem('collapseShiftsFilterSelect') !== 'hidden.bs.collapse') {
return;
}
@ -165,7 +187,9 @@ ready(() => {
localStorage.setItem('collapseShiftsFilterSelect', e.type);
};
$('#collapseShiftsFilterSelect')
.on('hidden.bs.collapse', onChange)
.on('shown.bs.collapse', onChange);
document.getElementById('collapseShiftsFilterSelect')
?.addEventListener('hidden.bs.collapse', onChange);
document.getElementById('collapseShiftsFilterSelect')
?.addEventListener('shown.bs.collapse', onChange);
});

View File

@ -1,34 +1,44 @@
import { ready } from './ready';
/**
* @param {NodeList} elements
* @param {string} styleProp
* @param {*} value
*/
const applyStyle = (elements, prop, value) => {
elements.forEach((element) => {
element.style[prop] = value;
});
}
/**
* Enables the fixed headers and time lane for the shift-calendar and datatables
*/
ready(function () {
if ($('.shift-calendar').length) {
const timeLanes = $('.shift-calendar .time');
const headers = $('.shift-calendar .header');
const topReference = $('.container-fluid .row');
timeLanes.css({
'position': 'relative',
'z-index': 999
});
headers.css({
'position': 'relative',
'z-index': 900
});
$(window).scroll(
function () {
const top = headers.parent().offset().top;
ready(() => {
if (!document.querySelector('.shift-calendar')) return;
const headers = document.querySelectorAll('.shift-calendar .header');
const timeLane = document.querySelector('.shift-calendar .time');
const topReference = document.querySelector('.container-fluid .row');
if (!headers.length || !timeLane || !topReference) return;
timeLane.style.position = 'relative';
timeLane.style.zIndex = 999;
applyStyle(headers, 'position', 'relative');
applyStyle(headers, 'zIndex', 900);
window.addEventListener('scroll', () => {
const top = headers.item(0).parentNode.getBoundingClientRect().top;
const left = 15;
timeLanes.css({
'left': Math.max(0, $(window).scrollLeft() - left) + 'px'
timeLane.style.left = Math.max(0, window.scrollX - left) + 'px';
const headersTop = Math.max(
0,
window.scrollY - top - 13 + topReference.getBoundingClientRect().top
) + 'px';
applyStyle(headers, 'top', headersTop);
});
headers.css({
'top': Math.max(0, $(window).scrollTop() - top
- 13
+ topReference.offset().top)
+ 'px'
});
});
}
});

View File

@ -1,11 +1,7 @@
require('core-js/stable');
import 'core-js/stable';
window.$ = window.jQuery = require('jquery');
window.bootstrap = require('bootstrap');
require('./forms');
require('./sticky-headers');
require('./countdown');
$.ajaxSetup({
headers: {'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')}
});
import './forms';
import './sticky-headers';
import './countdown';
import './dashboard';

View File

@ -249,13 +249,27 @@
</div>
</div>
<div class="col-md-3 col-lg-2">
Submit button<br>
Form submit
<form id="form">
{{ f.submit() }}
</form>
</div>
<div class="col-md-3 col-lg-2">
<div id="checkboxes" class="col-md-3 col-lg-2">
Checkbox<br>
{{ f.checkbox('form-input-checkbox', 'Checkbox', true) }}
{{ f.checkbox('form-input-checkbox-2', 'Checkbox 2') }}
{{ f.checkbox('form-input-checkbox', 'Checkbox 1', true, '1') }}
{{ f.checkbox('form-input-checkbox-2', 'Checkbox 2', false, '2') }}
{{ f.checkbox('form-input-checkbox-3', 'Checkbox 3', false, '3') }}
<div class="d-grid gap-2">
<div id="select-all-checkboxes" class="btn btn-sm btn-block btn-primary">
Select all
</div>
<div id="select-23-checkboxes" class="btn btn-sm btn-block btn-primary">
Select 2, 3
</div>
<div id="unselect-all-checkboxes" class="btn btn-sm btn-block btn-danger">
Unselect all
</div>
</div>
</div>
<div class="col-md-3 col-lg-2 checkbox-inline">
Radio<br>
@ -406,4 +420,22 @@
{{ bar_chart | raw }}
</div>
</div>
<script>
document.getElementById('select-all-checkboxes').addEventListener('click', () => {
checkAll('checkboxes', true);
});
document.getElementById('unselect-all-checkboxes').addEventListener('click', () => {
checkAll('checkboxes', false);
});
document.getElementById('select-23-checkboxes').addEventListener('click', () => {
checkOwnTypes('checkboxes', [2, 3]);
});
document.getElementById('form').addEventListener('submit', (e) => {
e.preventDefault();
return false;
});
</script>
{% endblock %}