diff --git a/plinth/modules/backups/forms.py b/plinth/modules/backups/forms.py
index 39388a0b4..4340587fe 100644
--- a/plinth/modules/backups/forms.py
+++ b/plinth/modules/backups/forms.py
@@ -54,7 +54,7 @@ class CreateArchiveForm(forms.Form):
regex=r'^[^{}/]*$', required=False, strip=True)
selected_apps = forms.MultipleChoiceField(
label=_('Included apps'), help_text=_('Apps to include in the backup'),
- widget=forms.CheckboxSelectMultiple)
+ widget=forms.CheckboxSelectMultiple(attrs={'class': 'has-select-all'}))
def __init__(self, *args, **kwargs):
"""Initialize the form with selectable apps."""
@@ -71,7 +71,7 @@ class CreateArchiveForm(forms.Form):
class RestoreForm(forms.Form):
selected_apps = forms.MultipleChoiceField(
label=_('Select the apps you want to restore'),
- widget=forms.CheckboxSelectMultiple)
+ widget=forms.CheckboxSelectMultiple(attrs={'class': 'has-select-all'}))
def __init__(self, *args, **kwargs):
"""Initialize the form with selectable apps."""
diff --git a/plinth/modules/backups/static/select_all.js b/plinth/modules/backups/static/select_all.js
deleted file mode 100644
index 01bd9d7d7..000000000
--- a/plinth/modules/backups/static/select_all.js
+++ /dev/null
@@ -1,66 +0,0 @@
-// 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 .
-
- @licend The above is the entire license notice
- for the JavaScript code in this page.
-*/
-
-function addSelectAll() {
- let ul = document.getElementById('id_backups-selected_apps');
- let li = document.createElement('li');
-
- let label = document.createElement('label');
- label.for = "select_all";
-
- let checkbox = document.createElement('input');
- checkbox.type = "checkbox";
- checkbox.checked = "checked";
- checkbox.id = "select-all";
-
- label.appendChild(checkbox);
- li.appendChild(label);
-
- ul.insertBefore(li, ul.childNodes[0]);
-};
-
-addSelectAll();
-
-/*
- * 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
- */
-$("#select-all").change(function() {
- $("[type=checkbox]").prop('checked', $(this).prop("checked"));
-});
-
-
-$('[type=checkbox]').change(function() {
- // If the rest of the checkbox items are checked check the "select all" checkbox as well
- if ($('[type=checkbox]:checked').length == ($('[type=checkbox]').length - 1)) {
- $("#select-all").prop('checked', true);
- }
- // Uncheck "select all" if one of the listed checkbox item is unchecked
- if (false == $(this).prop("checked")) {
- $("#select-all").prop('checked', false);
- }
-});
-
-
diff --git a/plinth/modules/backups/templates/backups_form.html b/plinth/modules/backups/templates/backups_form.html
index d8ca081b6..2e08d5ebf 100644
--- a/plinth/modules/backups/templates/backups_form.html
+++ b/plinth/modules/backups/templates/backups_form.html
@@ -21,7 +21,3 @@
{% endblock %}
-
-{% block page_js %}
-
-{% endblock %}
diff --git a/plinth/modules/backups/templates/backups_restore.html b/plinth/modules/backups/templates/backups_restore.html
index cf523553e..ac80828ae 100644
--- a/plinth/modules/backups/templates/backups_restore.html
+++ b/plinth/modules/backups/templates/backups_restore.html
@@ -29,7 +29,3 @@
{% endblock %}
-
-{% block page_js %}
-
-{% endblock %}
diff --git a/plinth/tests/functional/__init__.py b/plinth/tests/functional/__init__.py
index 7b3be695e..11fbd6018 100644
--- a/plinth/tests/functional/__init__.py
+++ b/plinth/tests/functional/__init__.py
@@ -454,7 +454,8 @@ def backup_create(browser, app_name, archive_name=None):
buttons = browser.find_link_by_href('/plinth/sys/backups/create/')
submit(browser, buttons.first)
- browser.find_by_id('select-all').uncheck()
+ eventually(browser.find_by_css, args=['.select-all'])
+ browser.find_by_css('.select-all').first.uncheck()
if archive_name:
browser.find_by_id('id_backups-name').fill(archive_name)
diff --git a/static/themes/default/js/main.js b/static/themes/default/js/main.js
index 5f7167a01..b9f6c5bf9 100644
--- a/static/themes/default/js/main.js
+++ b/static/themes/default/js/main.js
@@ -121,3 +121,66 @@ window.addEventListener('pageshow', function(event) {
element.remove();
}
});
+
+/*
+ * Select all option for multiple checkboxes.
+ */
+document.addEventListener('DOMContentLoaded', function(event) {
+ let parents = document.querySelectorAll('ul.has-select-all');
+ for (const parent of parents) {
+ let li = document.createElement('li');
+
+ let label = document.createElement('label');
+ label.for = "select_all";
+
+ let checkbox = document.createElement('input');
+ checkbox.type = "checkbox";
+ checkbox.setAttribute('class', 'select-all');
+
+ label.appendChild(checkbox);
+ li.appendChild(label);
+
+ parent.insertBefore(li, 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;
+}