From 00eb37e8acb0bee9bbc87e2c3fd50237cadd2b57 Mon Sep 17 00:00:00 2001 From: Iziram Date: Wed, 31 Jul 2024 13:43:39 +0200 Subject: [PATCH] change multi-select --- app/scodoc/sco_groups_view.py | 150 +++++---- app/scodoc/sco_utils.py | 119 ++++++- app/static/js/groups_view.js | 144 ++------- app/static/js/multi-select.js | 303 ++++++++++++++++++ .../pages/signal_assiduites_group.j2 | 20 +- app/templates/babase.j2 | 1 + app/views/assiduites.py | 4 +- app/views/groups.py | 6 +- 8 files changed, 557 insertions(+), 190 deletions(-) create mode 100644 app/static/js/multi-select.js diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index b4e6c32a5..ba15e0fca 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -56,12 +56,15 @@ from app.scodoc.gen_tables import GenTable from app.scodoc.sco_exceptions import ScoValueError, ScoPermissionDenied from app.scodoc.sco_permissions import Permission -JAVASCRIPTS = html_sco_header.BOOTSTRAP_MULTISELECT_JS + [ +JAVASCRIPTS = html_sco_header.BOOTSTRAP_JS + [ "js/etud_info.js", "js/groups_view.js", + "js/multi-select.js", ] -CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS +CSSSTYLES = html_sco_header.BOOTSTRAP_CSS + [ + "libjs/bootstrap/css/bootstrap.min.css", +] # view: @@ -115,13 +118,10 @@ def groups_view( return f""" { html_sco_header.sco_header( javascripts=JAVASCRIPTS, - cssstyles=CSSSTYLES, + cssstyles=CSSSTYLES ) } + + `; + + this.exportFormat = null; + this.observer = new MutationObserver(() => this.render()); + + this.toggleDropdown = this.toggleDropdown.bind(this); + this.handleDocumentClick = this.handleDocumentClick.bind(this); + + this._internals = this.attachInternals(); + this._internals.setFormValue([]); + } + + connectedCallback() { + this.render(); + this.observer.observe(this, { childList: true, subtree: true }); + const btn = this.shadowRoot.querySelector(".dropdown-button"); + btn.addEventListener("click", this.toggleDropdown); + document.addEventListener("click", this.handleDocumentClick); + + this._updateSelect(); + } + + disconnectedCallback() { + this.observer.disconnect(); + document.removeEventListener("click", this.handleDocumentClick); + } + + toggleDropdown(event) { + event.stopPropagation(); + const dropdownContent = this.shadowRoot.querySelector(".dropdown-content"); + dropdownContent.style.display = + dropdownContent.style.display === "block" ? "none" : "block"; + } + + handleDocumentClick(event) { + if (!this.contains(event.target)) { + this.shadowRoot.querySelector(".dropdown-content").style.display = "none"; + } + } + + render() { + const container = this.shadowRoot.querySelector(".multi-select-container"); + container.innerHTML = ""; + + const optgroups = this.querySelectorAll("optgroup"); + + optgroups.forEach((optgroup) => { + const groupDiv = document.createElement("div"); + groupDiv.className = "optgroup"; + + const groupLabel = document.createElement("div"); + groupLabel.textContent = optgroup.label; + groupDiv.appendChild(groupLabel); + + const options = optgroup.querySelectorAll("option"); + options.forEach((option) => { + const optionDiv = document.createElement("label"); + optionDiv.className = "option"; + + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.value = option.value; + checkbox.name = this.getAttribute("name"); + if (option.hasAttribute("selected")) { + checkbox.checked = true; + optionDiv.classList.add("selected"); + } + checkbox.addEventListener("change", () => { + this.handleCheckboxChange(checkbox); + }); + + optionDiv.appendChild(checkbox); + optionDiv.appendChild(document.createTextNode(option.textContent)); + groupDiv.appendChild(optionDiv); + }); + + container.appendChild(groupDiv); + }); + + this._updateSelect(); + } + + handleCheckboxChange(checkbox) { + const opt = this.querySelector(`option[value="${checkbox.value}"]`); + const isSingle = opt.hasAttribute("single"); + if (!checkbox.checked) { + checkbox.parentElement.classList.remove("selected"); + } else { + checkbox.parentElement.classList.add("selected"); + // Gestion de l'option "single" + if (isSingle) { + // Uncheck all other checkboxes + const checkboxes = this.shadowRoot.querySelectorAll( + 'input[type="checkbox"]' + ); + checkboxes.forEach((cb) => { + if (cb !== checkbox) { + cb.checked = false; + cb.parentElement.classList.remove("selected"); + } + }); + } else { + // Uncheck the single checkbox if present + const singleCheckbox = Array.from( + this.shadowRoot.querySelectorAll('input[type="checkbox"]') + ).find((cb) => + this.querySelector(`option[value="${cb.value}"]`).hasAttribute( + "single" + ) + ); + if (singleCheckbox) { + singleCheckbox.checked = false; + singleCheckbox.parentElement.classList.remove("selected"); + } + } + } + this._updateSelect(); + } + + _updateSelect() { + const checkboxes = this.shadowRoot.querySelectorAll( + 'input[type="checkbox"]' + ); + const checkedBoxes = Array.from(checkboxes).filter( + (checkbox) => checkbox.checked + ); + + const values = checkedBoxes.map((checkbox) => checkbox.value); + + const opts = checkedBoxes.map((checkbox) => { + return this.querySelector(`option[value="${checkbox.value}"]`); + }); + + const btn = this.shadowRoot.querySelector(".dropdown-button"); + + if (checkedBoxes.length === 0) { + btn.textContent = this.label || "Select options"; + } else if (checkedBoxes.length < 4) { + btn.textContent = opts.map((opt) => opt.textContent).join(", ") + ""; + } else { + btn.textContent = `${checkedBoxes.length} sélections`; + } + + btn.textContent += " ⮛"; + + this._values(values); + + this.dispatchEvent(new Event("change")); + } + + _values(newValues = null) { + const checkboxes = this.shadowRoot.querySelectorAll( + 'input[type="checkbox"]' + ); + if (newValues === null) { + // Get selected values + const values = Array.from(checkboxes) + .filter((checkbox) => checkbox.checked) + .map((checkbox) => checkbox.value); + if (this.exportFormat) { + return this.exportFormat(values); + } + return values; + } else { + // Set selected values + checkboxes.forEach((checkbox) => { + checkbox.checked = newValues.includes(checkbox.value); + }); + + this._internals.setFormValue(this._values()); + } + } + + get value() { + return this._values(); + } + + set value(values) { + this._values(values); + } + + on(callback) { + this.addEventListener("change", callback); + } + + format(callback) { + this.exportFormat = callback; + } +} + +customElements.define("multi-select", MultiSelect); diff --git a/app/templates/assiduites/pages/signal_assiduites_group.j2 b/app/templates/assiduites/pages/signal_assiduites_group.j2 index fddae39c1..b939944a7 100644 --- a/app/templates/assiduites/pages/signal_assiduites_group.j2 +++ b/app/templates/assiduites/pages/signal_assiduites_group.j2 @@ -7,7 +7,6 @@ {% block scripts %} {{ super() }} - @@ -73,9 +72,10 @@ creerTousLesEtudiants(etuds); }); - $("#group_ids_sel").on("change", ()=>{ + const group_sel = document.querySelector("#group_ids_sel") + group_sel.on((values)=>{ main(); - }) + }); const moduleimpls = {}; const inscriptionsModules = new Map(); @@ -83,7 +83,8 @@ async function main(){ dateCouranteEstTravaillee(); - etuds = await recupEtuds($('#group_ids_sel').val()); + let group_ids = group_sel.value; + etuds = await recupEtuds(group_ids); if (etuds.size != 0){ await recupAssiduites(etuds, $("#date").datepicker("getDate")); } @@ -101,8 +102,7 @@ {% block styles %} {{ super() }} - - +{# #} @@ -153,12 +153,12 @@
Groupes : {{grp|safe}}
-
-
@@ -166,8 +166,8 @@
{{timeline|safe}}
- - + +
diff --git a/app/templates/babase.j2 b/app/templates/babase.j2 index 2dc3aa89a..94baa48db 100644 --- a/app/templates/babase.j2 +++ b/app/templates/babase.j2 @@ -33,6 +33,7 @@ + diff --git a/app/views/assiduites.py b/app/views/assiduites.py index 10e092cba..c1d210ab6 100644 --- a/app/views/assiduites.py +++ b/app/views/assiduites.py @@ -94,7 +94,7 @@ from app.tables.visu_assiduites import TableAssi, etuds_sorted_from_ids from app.scodoc.sco_archives_justificatifs import JustificatifArchiver -CSSSTYLES = html_sco_header.BOOTSTRAP_MULTISELECT_CSS +CSSSTYLES = html_sco_header.BOOTSTRAP_CSS # -------------------------------------------------------------------- @@ -1164,7 +1164,7 @@ def signal_assiduites_group(): formsemestre_date_fin=str(formsemestre.date_fin), formsemestre_id=formsemestre_id, gr_tit=gr_tit, - grp=sco_groups_view.menu_groups_choice(groups_infos), + grp=sco_groups_view.menu_groups_choice(groups_infos, html_export=False), minitimeline=_mini_timeline(), moduleimpl_select=_module_selector(formsemestre, moduleimpl_id), nonworkdays=_non_work_days(), diff --git a/app/views/groups.py b/app/views/groups.py index ae6bca0e7..4f8e815dc 100644 --- a/app/views/groups.py +++ b/app/views/groups.py @@ -45,7 +45,7 @@ def formulaire_feuille_appel(): formsemestre: FormSemestre = FormSemestre.get_formsemestre(formsemestre_id) - group_ids: list[int] = request.args.get("group_ids", "").split(",") + group_ids: list[int] = request.args.getlist("group_ids") form: groups_form.FeuilleAppelPreForm = groups_form.FeuilleAppelPreForm( request.form @@ -65,7 +65,7 @@ def formulaire_feuille_appel(): "ens": form.ens.data or "", } - form_group_ids: list[str] = request.form.getlist("group_ids") + form_group_ids: list[str] = request.form.get("group_ids", "").split(",") if form_group_ids: groups_infos = DisplayedGroupsInfos( form_group_ids, @@ -89,5 +89,5 @@ def formulaire_feuille_appel(): sco_data=ScoData(formsemestre=formsemestre), form=form, group_name=groups_infos.groups_titles, - grp=menu_groups_choice(groups_infos), + grp=menu_groups_choice(groups_infos, html_export=False), )