Sunil Mohan Adapa a150cd15bd
static: js: css: Make multiple select fields work with Django 4.0
Closes: #2228.

- Django 4.0 changed to using <div> instead of using <ul> and <li> for multiple
choice select fields. Update code for the select-all button to work with the new
HTML structure.

- Add styling to ensure that multiple choice select field appears similar to
previous <ul> and <li> based style.

- This patch assumes that django-bootstrap-form has support for Django 4.0 as
seen in https://github.com/tzangms/django-bootstrap-form/pull/110 .

Tests:

- Radio select seem to have no issues. Checked in networks -> connection type
page.

- Open Backups -> Create backup page and ensure that select all button works
and appears same on testing (Django 3.2) and unstable (Django 4.0).

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
2022-07-04 21:03:02 -04:00

193 lines
7.0 KiB
JavaScript

// SPDX-License-Identifier: AGPL-3.0-or-later
/*
This file is part of FreedomBox.
@licstart The following is the entire license notice for the
JavaScript code in this page.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
@licend The above is the entire license notice
for the JavaScript code in this page.
*/
/*
* Refresh page if marked for refresh.
*/
document.addEventListener('DOMContentLoaded', function() {
const body = document.querySelector('body');
if (body.hasAttribute('data-refresh-page-sec')) {
let seconds = body.getAttribute('data-refresh-page-sec');
seconds = parseInt(seconds, 10);
if (isNaN(seconds))
return;
window.setTimeout(() => {
// Refresh the page without resubmitting the POST data.
window.location = window.location.href;
}, seconds * 1000);
}
});
/*
* Return all submit buttons on the page
*/
function getSubmitButtons(){
return document.querySelectorAll(
"form input[type='submit'], form button[type='submit'].toggle-button");
}
/*
* Disable submit button on click.
*/
function onSubmitAddProgress(event) {
// Using activeElement is not reliable. If the user presses Enter on a text
// field, activeElement with be that text field. However, we do safety
// checks and fallback to not disabling/animating the submit button, which
// is okay.
button = document.activeElement;
if (!button.classList.contains('btn') ||
button.classList.contains('btn-link') ||
button.classList.contains('no-running-status') ||
button.classList.contains('pull-right') ||
button.hasAttribute('disabled')) {
return;
}
// Don't disable the submit button immediately as that will prevent the
// button from being sent in the HTTP request. Instead schedule disabling
// for the next event loop run which will happen after current event is
// processed.
window.setTimeout(() => {
if (button.tagName == "INPUT"){
// For push buttons
const beforeElement = document.createElement('div');
beforeElement.classList.add('running-status-button-before');
button.parentNode.insertBefore(beforeElement, button);
} else if (button.tagName == "BUTTON"){
// For toggle buttons
button.classList.toggle('toggle-button--toggled');
}
button.classList.add('running-status-button');
// Disable all form submit buttons on the page
for (const formbutton of getSubmitButtons()) {
if (!(formbutton.classList.contains('btn-link') ||
formbutton.classList.contains('no-running-status') ||
formbutton.hasAttribute('disabled'))) {
formbutton.classList.add('temporarily-disabled');
formbutton.setAttribute('disabled', 'disabled');
}
}
}, 0);
}
document.addEventListener('DOMContentLoaded', function(event) {
for (const button of getSubmitButtons()) {
// Don't listen for 'click' event on buttons as they are triggered
// even when the form is invalid.
button.form.addEventListener('submit', onSubmitAddProgress);
}
});
// When using back/forward browser's bfcache is used and pages won't receive
// 'load' events. Instead a 'pageshow' event is available. When a user does
// back/forward we want them to be able to submit the forms again. So clear all
// the button disabling.
window.addEventListener('pageshow', function(event) {
for (const button of getSubmitButtons()) {
button.classList.remove('running-status-button');
if (button.classList.contains('temporarily-disabled')) {
button.classList.remove('temporarily-disabled');
button.removeAttribute('disabled');
}
}
const beforeSelector = ".running-status-button-before";
const beforeElements = document.querySelectorAll(beforeSelector);
for (const element of beforeElements) {
element.remove();
}
});
/*
* Select all option for multiple checkboxes.
*/
document.addEventListener('DOMContentLoaded', function(event) {
// Django < 4.0 generates <ul> and <li> where as Django >= 4.0 generates <div>s
let parents = document.querySelectorAll('ul.has-select-all,div.has-select-all');
for (const parent of parents) {
let childElementType = 'div';
if (parent.tagName.toLowerCase() == 'ul')
childElementType = 'li';
let selectAllItem = document.createElement(childElementType);
let label = document.createElement('label');
label.for = "select_all";
label.setAttribute('class', 'select-all-label');
let checkbox = document.createElement('input');
checkbox.type = "checkbox";
checkbox.setAttribute('class', 'select-all');
label.appendChild(checkbox);
selectAllItem.appendChild(label);
parent.insertBefore(selectAllItem, parent.childNodes[0]);
setSelectAllValue(parent);
checkbox.addEventListener('change', onSelectAllChanged);
options = parent.querySelectorAll('input.has-select-all');
for (const option of options) {
option.addEventListener('change', onSelectAllOptionsChanged);
}
}
});
// When there is a change on the "select all" checkbox, set the checked property
// of all the checkboxes to the value of the "select all" checkbox
function onSelectAllChanged(event) {
const selectAllCheckbox = event.currentTarget;
const parent = selectAllCheckbox.parentElement.parentElement.parentElement;
const options = parent.querySelectorAll('input.has-select-all');
for (const option of options) {
option.checked = selectAllCheckbox.checked;
}
}
// When there is a change on a checkbox controlled by a select all checkbox,
// update the value of checkbox.
function onSelectAllOptionsChanged(event) {
const parent = event.currentTarget.parentElement.parentElement.parentElement;
setSelectAllValue(parent);
}
// Set/reset the checked property of "select all" checkbox based on whether all
// checkboxes it controls are checked.
function setSelectAllValue(parent) {
const options = parent.querySelectorAll('input.has-select-all');
let enableSelectAll = true;
for (const option of options) {
if (!option.checked) {
enableSelectAll = false;
break;
}
}
parent.querySelector('.select-all').checked = enableSelectAll;
}