diff --git a/app/forms/multiselect.py b/app/forms/multiselect.py new file mode 100644 index 0000000000000000000000000000000000000000..77b13626b58b090dc96ae3f9501800eabbc15b2e --- /dev/null +++ b/app/forms/multiselect.py @@ -0,0 +1,118 @@ +""" +Simplification des multiselect HTML/JS +""" + + +class MultiSelect: + """ + Classe pour faciliter l'utilisation du multi-select HTML/JS + + Les values sont représentées en dict { + value: "...", + label:"...", + selected: True/False (default to False), + single: True/False (default to False) + } + + Args: + values (dict[str, list[dict]]): Dictionnaire des valeurs + génère des <optgroup> pour chaque clef du dictionnaire + génère des <option> pour chaque valeur du dictionnaire + name (str, optional): Nom du multi-select. Defaults to "multi-select". + html_id (str, optional): Id HTML du multi-select. Defaults to "multi-select". + classname (str, optional): Classe CSS du multi-select. Defaults to "". + label (str, optional): Label du multi-select. Defaults to "". + export (str, optional): Format du multi-select (HTML/JS). Defaults to "js". + HTML : group_ids="val1"&group_ids="val2"... + JS : ["val1","val2", ...] + + **kwargs: Arguments supplémentaires (appliqué au multiselect en HTML <multi-select key="value" ...>) + """ + + def __init__( + self, + values: dict[str, list[dict]], + name="multi-select", + html_id="multi-select", + label="", + classname="", + **kwargs, + ) -> None: + self.values: dict[str, list[dict]] = values + self._on = "" + + self.name: str = name + self.html_id: str = html_id + self.classname: str = classname + self.label: str = label or name + + self.args: dict = kwargs + self.js: str = "" + self.export: str = "return values" + + def html(self) -> str: + """ + Génère l'HTML correspondant au multi-select + """ + opts: list[str] = [] + + for key, values in self.values.items(): + optgroup = f"<optgroup label='{key}'>" + for value in values: + selected = "selected" if value.get("selected", False) else "" + single = "single" if value.get("single", False) else "" + opt = f"<option value='{value.get('value')}' {selected} {single} >{value.get('label')}</option>" + optgroup += opt + optgroup += "</optgroup>" + opts.append(optgroup) + + args: list[str] = [f'{key}="{value}"' for key, value in self.args.items()] + js: str = "{" + self.js + "}" + export: str = "{" + self.export + "}" + return f""" + <multi-select + label="{self.label}" + id="{self.html_id}" + name="{self.name}" + class="{self.classname}" + {" ".join(args)} + > + {"".join(opts)} + </multi-select> + <script> + window.addEventListener('load', () => {{document.getElementById("{self.html_id}").on((values)=>{js}); + document.getElementById("{self.html_id}").format((values)=>{export});}} ); + </script> + """ + + def change_event(self, js: str) -> None: + """ + Ajoute un évènement de changement au multi-select + + CallBack JS : (event) => {/*actions à effectuer*/} + + Sera retranscrit dans l'HTML comme : + + document.getElementById(%self.id%).on((event)=>{%self.js%}) + + Exemple d'utilisation : + + js : "console.log(event.target.value)" + """ + self.js: str = js + + def export_format(self, js: str) -> None: + """ + Met à jour le format de retour de valeur du multi-select + + CallBack JS : (values) => {/*actions à effectuer*/} + + Sera retranscrit dans l'HTML comme : + + document.getElementById(%self.id%).format((values)=>{%self.js%}) + + Exemple d'utilisation : + + js : "return values.map(v=> 'val:'+v)" + """ + self.export: str = js diff --git a/app/scodoc/sco_groups_view.py b/app/scodoc/sco_groups_view.py index ba15e0fca4ed10be2b4a3e37895dbd78866c4527..2988dc3ea13d17439e1a5b9714dd0b67f21efcae 100644 --- a/app/scodoc/sco_groups_view.py +++ b/app/scodoc/sco_groups_view.py @@ -750,7 +750,7 @@ def groups_table( name="options", html_id="group_list_options", ) - multi_select.change_event("change_list_options(values)") + multi_select.change_event("change_list_options(event.target.value);") H.extend( # ; [ diff --git a/app/scodoc/sco_utils.py b/app/scodoc/sco_utils.py index a97834cb0ec2032daf134f90d3d9e1a0e05770ba..a51790c793f3da2224b3a0784bb201757e63fd43 100644 --- a/app/scodoc/sco_utils.py +++ b/app/scodoc/sco_utils.py @@ -63,6 +63,8 @@ from werkzeug.http import HTTP_STATUS_CODES from config import Config from app import log, ScoDocJSONEncoder +from app.forms.multiselect import MultiSelect + from app.scodoc.codes_cursus import NOTES_TOLERANCE, CODES_EXPL from app.scodoc.sco_exceptions import ScoValueError from app.scodoc import sco_xml @@ -445,121 +447,6 @@ def translate_assiduites_metric(metric, inverse=True, short=True) -> str: return None -class MultiSelect: - """ - Classe pour faciliter l'utilisation du multi-select HTML/JS - - Les values sont représentées en dict { - value: "...", - label:"...", - selected: True/False (default to False), - single: True/False (default to False) - } - - Args: - values (dict[str, list[dict]]): Dictionnaire des valeurs - génère des <optgroup> pour chaque clef du dictionnaire - génère des <option> pour chaque valeur du dictionnaire - name (str, optional): Nom du multi-select. Defaults to "multi-select". - html_id (str, optional): Id HTML du multi-select. Defaults to "multi-select". - classname (str, optional): Classe CSS du multi-select. Defaults to "". - label (str, optional): Label du multi-select. Defaults to "". - export (str, optional): Format du multi-select (HTML/JS). Defaults to "js". - HTML : group_ids="val1"&group_ids="val2"... - JS : ["val1","val2", ...] - - **kwargs: Arguments supplémentaires (appliqué au multiselect en HTML <multi-select key="value" ...>) - """ - - def __init__( - self, - values: dict[str, list[dict]], - name="multi-select", - html_id="multi-select", - label="", - classname="", - **kwargs, - ) -> None: - self.values: dict[str, list[dict]] = values - self._on = "" - - self.name: str = name - self.html_id: str = html_id - self.classname: str = classname - self.label: str = label or name - - self.args: dict = kwargs - self.js: str = "" - self.export: str = "return values" - - def html(self) -> str: - """ - Génère l'HTML correspondant au multi-select - """ - opts: list[str] = [] - - for key, values in self.values.items(): - optgroup = f"<optgroup label='{key}'>" - for value in values: - selected = "selected" if value.get("selected", False) else "" - single = "single" if value.get("single", False) else "" - opt = f"<option value='{value.get('value')}' {selected} {single} >{value.get('label')}</option>" - optgroup += opt - optgroup += "</optgroup>" - opts.append(optgroup) - - args: list[str] = [f'{key}="{value}"' for key, value in self.args.items()] - js: str = "{" + self.js + "}" - export: str = "{" + self.export + "}" - return f""" - <multi-select - label="{self.label}" - id="{self.html_id}" - name="{self.name}" - class="{self.classname}" - {" ".join(args)} - > - {"".join(opts)} - </multi-select> - <script> - window.addEventListener('load', () => {{document.getElementById("{self.html_id}").on((values)=>{js}); - document.getElementById("{self.html_id}").format((values)=>{export});}} ); - </script> - """ - - def change_event(self, js: str) -> None: - """ - Met à jour l'évènement de changement de valeur du multi-select - - CallBack JS : (values) => {/*actions à effectuer*/} - - Sera retranscrit dans l'HTML comme : - - document.getElementById(%self.id%).on((values)=>{%self.js%}) - - Exemple d'utilisation : - - js : "console.log(values)" - """ - self.js: str = js - - def export_format(self, js: str) -> None: - """ - Met à jour le format de retour de valeur du multi-select - - CallBack JS : (values) => {/*actions à effectuer*/} - - Sera retranscrit dans l'HTML comme : - - document.getElementById(%self.id%).format((values)=>{%self.js%}) - - Exemple d'utilisation : - - js : "return values.map(v=> 'val:'+v)" - """ - self.export: str = js - - # Types de modules class ModuleType(IntEnum): """Code des types de module.""" diff --git a/app/static/js/multi-select.js b/app/static/js/multi-select.js index e10194ad24cfe753ae7252b90f6a471827d04d46..178586f81c94422d28c23aa505ded11c9c9b345b 100644 --- a/app/static/js/multi-select.js +++ b/app/static/js/multi-select.js @@ -76,18 +76,28 @@ class MultiSelect extends HTMLElement { position: absolute; background-color: #fff; min-width: 200px; - max-height: 200px; - overflow-y: auto; box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); z-index: 1; } .dropdown-content .optgroup { - padding: 10px; + padding: 4px 8px; width: 100%; } .dropdown-content .optgroup div { font-weight: bold; } + .dropdown-button::after{ + content: ""; + display: inline-block; + width: 0; + height: 0; + margin-left: 4px; + vertical-align: middle; + border-top: 4px dashed; + border-right: 4px solid transparent; + border-left: 4px solid transparent; + } + .dropdown-content .option { display: flex; align-items: center; @@ -252,11 +262,6 @@ class MultiSelect extends HTMLElement { } else { btn.textContent = `${checkedBoxes.length} sélections`; } - - btn.textContent += " ⮛"; - - this._values(values); - this.dispatchEvent(new Event("change")); } @@ -280,6 +285,7 @@ class MultiSelect extends HTMLElement { }); this._internals.setFormValue(this._values()); + this._updateSelect(); } }