forked from ScoDoc/ScoDoc
change multi-select
This commit is contained in:
parent
7f08f84934
commit
00eb37e8ac
@ -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
|
||||
)
|
||||
}
|
||||
<style>
|
||||
div.multiselect-container.dropdown-menu {{
|
||||
min-width: 180px;
|
||||
}}
|
||||
span.warning_unauthorized {{
|
||||
color: pink;
|
||||
font-style: italic;
|
||||
@ -221,47 +221,70 @@ def form_groups_choice(
|
||||
|
||||
|
||||
def menu_groups_choice(
|
||||
groups_infos, submit_on_change=False, default_deselect_others=True
|
||||
groups_infos,
|
||||
submit_on_change=False,
|
||||
default_deselect_others=True,
|
||||
html_export=True,
|
||||
change_event=None,
|
||||
):
|
||||
"""menu pour selection groupes
|
||||
group_ids est la liste des groupes actuellement sélectionnés
|
||||
et doit comporter au moins un élément, sauf si formsemestre_id est spécifié.
|
||||
(utilisé pour retrouver le semestre et proposer la liste des autres groupes)
|
||||
|
||||
Si html_export :
|
||||
selecteur.value = &group_ids=xxx&group_ids=yyy...
|
||||
sinon :
|
||||
selecteur.value = [xxx, yyy, ...]
|
||||
|
||||
Si change_event :
|
||||
met à jour l'événement onchange du selecteur
|
||||
(attend du js, plus d'informations sur scu.MultiSelect.change_event)
|
||||
|
||||
"""
|
||||
default_group_id = sco_groups.get_default_group(groups_infos.formsemestre_id)
|
||||
n_members = len(sco_groups.get_group_members(default_group_id))
|
||||
|
||||
H = [
|
||||
f"""<select name="group_ids" id="group_ids_sel"
|
||||
class="multiselect
|
||||
{'submit_on_change' if submit_on_change else ''}
|
||||
{'default_deselect_others' if default_deselect_others else ''}
|
||||
"
|
||||
multiple="multiple">
|
||||
<option class="default_group"
|
||||
value="{default_group_id}"
|
||||
{'selected' if default_group_id in groups_infos.group_ids else ''}
|
||||
>Tous ({n_members})</option>
|
||||
"""
|
||||
]
|
||||
values: dict = {
|
||||
# Choix : Tous (tous les groupes)
|
||||
"": [
|
||||
{
|
||||
"value": default_group_id,
|
||||
"label": f"Tous ({n_members})",
|
||||
"selected": default_group_id in groups_infos.group_ids,
|
||||
"single": default_deselect_others,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
for partition in groups_infos.partitions:
|
||||
H.append('<optgroup label="%s">' % partition["partition_name"])
|
||||
p_name: str = partition["partition_name"]
|
||||
vals: list[tuple[str, str, bool]] = []
|
||||
# Les groupes dans cette partition:
|
||||
for g in sco_groups.get_partition_groups(partition):
|
||||
if g["group_id"] in groups_infos.group_ids:
|
||||
selected = "selected"
|
||||
else:
|
||||
selected = ""
|
||||
if g["group_name"]:
|
||||
n_members = len(sco_groups.get_group_members(g["group_id"]))
|
||||
H.append(
|
||||
'<option value="%s" %s>%s (%s)</option>'
|
||||
% (g["group_id"], selected, g["group_name"], n_members)
|
||||
for grp in sco_groups.get_partition_groups(partition):
|
||||
selected: bool = grp["group_id"] in groups_infos.group_ids
|
||||
if grp["group_name"]:
|
||||
vals.append(
|
||||
{
|
||||
"value": grp["group_id"],
|
||||
"label": f"{grp['group_name']} ({len(sco_groups.get_group_members(grp['group_id']))})",
|
||||
"selected": selected,
|
||||
}
|
||||
)
|
||||
H.append("</optgroup>")
|
||||
H.append("</select> ")
|
||||
return "\n".join(H)
|
||||
|
||||
values[p_name] = vals
|
||||
|
||||
multi_select: scu.MultiSelect = scu.MultiSelect(
|
||||
values=values, name="group_ids", html_id="group_ids_sel"
|
||||
)
|
||||
|
||||
if html_export:
|
||||
multi_select.export_format('return "&group_ids="+values.join("&group_ids=")')
|
||||
|
||||
if submit_on_change:
|
||||
multi_select.change_event("submit_group_selector();")
|
||||
|
||||
return multi_select.html()
|
||||
|
||||
|
||||
def menu_group_choice(group_id=None, formsemestre_id=None):
|
||||
@ -698,7 +721,6 @@ def groups_table(
|
||||
"""
|
||||
]
|
||||
if groups_infos.members:
|
||||
menu_options = []
|
||||
options = {
|
||||
"with_codes": "Affiche codes",
|
||||
}
|
||||
@ -711,34 +733,33 @@ def groups_table(
|
||||
"with_bourse": "Statut boursier",
|
||||
}
|
||||
)
|
||||
valeurs: list[tuple[str, str]] = []
|
||||
for option, label in options.items():
|
||||
if locals().get(option, False):
|
||||
selected = "selected"
|
||||
else:
|
||||
selected = ""
|
||||
menu_options.append(
|
||||
f"""<option value="{option}" {selected}>{label}</option>"""
|
||||
selected = locals().get(option, False)
|
||||
valeurs.append(
|
||||
{
|
||||
"value": option,
|
||||
"label": label,
|
||||
"selected": selected,
|
||||
}
|
||||
)
|
||||
|
||||
multi_select: scu.MultiSelect = scu.MultiSelect(
|
||||
values={"": valeurs},
|
||||
label="Options",
|
||||
name="options",
|
||||
html_id="group_list_options",
|
||||
)
|
||||
multi_select.change_event("change_list_options(values)")
|
||||
H.extend(
|
||||
# ;
|
||||
[
|
||||
"""<span style="margin-left: 2em;">
|
||||
<select name="group_list_options" id="group_list_options" class="multiselect" multiple="multiple">""",
|
||||
"\n".join(menu_options),
|
||||
"""</select></span>
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$('#group_list_options').multiselect(
|
||||
{
|
||||
includeSelectAllOption: false,
|
||||
nonSelectedText:'Options...',
|
||||
onChange: function(element, checked){
|
||||
change_list_options();
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
f"""
|
||||
<span style="margin-left: 2em;">
|
||||
|
||||
{multi_select.html()}
|
||||
|
||||
</span>
|
||||
""",
|
||||
(
|
||||
"""<span class="warning_unauthorized">accès aux données personnelles interdit</span>"""
|
||||
@ -929,11 +950,14 @@ def tab_absences_html(groups_infos, etat=None):
|
||||
"""
|
||||
]
|
||||
|
||||
url_feuille_appel: str = url_for(
|
||||
"scolar.formulaire_feuille_appel",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=groups_infos.formsemestre_id,
|
||||
group_ids=group_ids,
|
||||
url_feuille_appel: str = (
|
||||
url_for(
|
||||
"scolar.formulaire_feuille_appel",
|
||||
scodoc_dept=g.scodoc_dept,
|
||||
formsemestre_id=groups_infos.formsemestre_id,
|
||||
)
|
||||
+ "&"
|
||||
+ groups_infos.groups_query_args
|
||||
)
|
||||
|
||||
H.extend(
|
||||
|
@ -26,8 +26,8 @@
|
||||
##############################################################################
|
||||
|
||||
|
||||
""" Common definitions
|
||||
"""
|
||||
"""Common definitions"""
|
||||
|
||||
import base64
|
||||
import bisect
|
||||
import collections
|
||||
@ -445,6 +445,121 @@ 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."""
|
||||
|
@ -13,72 +13,41 @@ $().ready(function () {
|
||||
// (ne fonctionne que pour les requetes GET: manipule la query string)
|
||||
|
||||
function groups_view_url() {
|
||||
var url = $.url();
|
||||
delete url.param()["group_ids"]; // retire anciens groupes de l'URL
|
||||
delete url.param()["curtab"]; // retire ancien tab actif
|
||||
if (CURRENT_TAB_HASH) {
|
||||
url.param()["curtab"] = CURRENT_TAB_HASH;
|
||||
}
|
||||
delete url.param()["formsemestre_id"];
|
||||
url.param()["formsemestre_id"] =
|
||||
$("#group_selector")[0].formsemestre_id.value;
|
||||
|
||||
var selected_groups = $("#group_selector select#group_ids_sel").val();
|
||||
url.param()["group_ids"] = selected_groups; // remplace par groupes selectionnes
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
// Sélectionne le groupe "tous" et recharge la page:
|
||||
function select_groupe_tous() {
|
||||
var url = groups_view_url();
|
||||
var default_group_id = $("#group_selector")[0].default_group_id.value;
|
||||
delete url.param()["group_ids"];
|
||||
url.param()["group_ids"] = [default_group_id];
|
||||
|
||||
var query_string = $.param(url.param(), (traditional = true));
|
||||
window.location = url.attr("base") + url.attr("path") + "?" + query_string;
|
||||
let url = new URL(location.href);
|
||||
let urlParams = url.searchParams;
|
||||
// ajout du formsemestre
|
||||
urlParams.set(
|
||||
"formsemestre_id",
|
||||
$("#group_selector")[0].formsemestre_id.value
|
||||
);
|
||||
// ajout du tab actif
|
||||
const tabActif = document.querySelector(
|
||||
'[role="tab"][aria-selected="true"]'
|
||||
).id;
|
||||
urlParams.set("tab", tabActif);
|
||||
urlParams.delete("group_ids");
|
||||
// ajout des groupes selectionnes
|
||||
var selected_groups = document.getElementById("group_ids_sel").value;
|
||||
url.search = urlParams.toString() + selected_groups;
|
||||
return url.href;
|
||||
}
|
||||
|
||||
// Recharge la page sans arguments group_ids
|
||||
function remove_group_filter() {
|
||||
var url = groups_view_url();
|
||||
delete url.param()["group_ids"];
|
||||
var query_string = $.param(url.param(), (traditional = true));
|
||||
window.location = url.attr("base") + url.attr("path") + "?" + query_string;
|
||||
}
|
||||
var url = new URL(location.href);
|
||||
url.searchParams.delete("group_ids");
|
||||
|
||||
// L'URL pour l'état courant de la page:
|
||||
function get_current_url() {
|
||||
var url = groups_view_url();
|
||||
var query_string = $.param(url.param(), (traditional = true));
|
||||
return url.attr("base") + url.attr("path") + "?" + query_string;
|
||||
window.location = url.href;
|
||||
}
|
||||
|
||||
// Recharge la page en changeant les groupes selectionnés et en conservant le tab actif:
|
||||
function submit_group_selector() {
|
||||
window.location = get_current_url();
|
||||
window.location = groups_view_url();
|
||||
}
|
||||
|
||||
function show_current_tab() {
|
||||
if (document.getElementsByClassName("nav-tabs").length > 0) {
|
||||
$('.nav-tabs [href="#' + CURRENT_TAB_HASH + '"]').tab("show");
|
||||
}
|
||||
}
|
||||
|
||||
var CURRENT_TAB_HASH = $.url().param()["curtab"];
|
||||
|
||||
$().ready(function () {
|
||||
$(".nav-tabs a").on("shown.bs.tab", function (e) {
|
||||
CURRENT_TAB_HASH = e.target.hash.slice(1); // sans le #
|
||||
});
|
||||
|
||||
show_current_tab();
|
||||
});
|
||||
|
||||
function change_list_options() {
|
||||
var url = groups_view_url();
|
||||
var selected_options = $("#group_list_options").val();
|
||||
function change_list_options(selected_options) {
|
||||
var url = new URL(groups_view_url());
|
||||
var urlParams = url.searchParams;
|
||||
var options = [
|
||||
"with_paiement",
|
||||
"with_archives",
|
||||
@ -88,13 +57,11 @@ function change_list_options() {
|
||||
];
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var option = options[i];
|
||||
delete url.param()[option];
|
||||
if ($.inArray(option, selected_options) >= 0) {
|
||||
url.param()[option] = 1;
|
||||
urlParams.set(option, "1");
|
||||
}
|
||||
}
|
||||
var query_string = $.param(url.param(), (traditional = true));
|
||||
window.location = url.attr("base") + url.attr("path") + "?" + query_string;
|
||||
window.location = url.href;
|
||||
}
|
||||
|
||||
// Menu choix groupe:
|
||||
@ -134,57 +101,6 @@ function toggle_visible_etuds() {
|
||||
.forEach((elem) => (elem.value = group_ids_str));
|
||||
}
|
||||
|
||||
$().ready(function () {
|
||||
$("#group_ids_sel").multiselect({
|
||||
includeSelectAllOption: false,
|
||||
nonSelectedText: "choisir...",
|
||||
// buttonContainer: '<div id="group_ids_sel_container"/>',
|
||||
onChange: function (element, checked) {
|
||||
// Gestion du groupe "tous"
|
||||
if (
|
||||
checked == true &&
|
||||
$("#group_ids_sel").hasClass("default_deselect_others")
|
||||
) {
|
||||
var default_group_id = $(".default_group")[0].value;
|
||||
|
||||
if (element.hasClass("default_group")) {
|
||||
// click sur groupe "tous"
|
||||
// deselectionne les autres
|
||||
$("#group_ids_sel option:selected").each(function (index, opt) {
|
||||
if (opt.value != default_group_id) {
|
||||
$("#group_ids_sel").multiselect("deselect", opt.value);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// click sur un autre item
|
||||
// si le groupe "tous" est selectionne et que l'on coche un autre, le deselectionner
|
||||
var default_is_selected = false;
|
||||
$("#group_ids_sel option:selected").each(function (index, opt) {
|
||||
if (opt.value == default_group_id) {
|
||||
default_is_selected = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
if (default_is_selected) {
|
||||
$("#group_ids_sel").multiselect("deselect", default_group_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
toggle_visible_etuds();
|
||||
// referme le menu apres chaque choix:
|
||||
$("#group_selector .btn-group").removeClass("open");
|
||||
|
||||
if ($("#group_ids_sel").hasClass("submit_on_change")) {
|
||||
submit_group_selector();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
// initial setup
|
||||
toggle_visible_etuds();
|
||||
});
|
||||
|
||||
// Trombinoscope
|
||||
$().ready(function () {
|
||||
var elems = $(".trombi-photo");
|
||||
@ -211,3 +127,11 @@ $().ready(function () {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// gestion du tab actif
|
||||
$().ready(function () {
|
||||
let tab = new URL(location.href).searchParams.get("tab");
|
||||
if (tab) {
|
||||
document.getElementById(tab)?.click();
|
||||
}
|
||||
});
|
||||
|
303
app/static/js/multi-select.js
Normal file
303
app/static/js/multi-select.js
Normal file
@ -0,0 +1,303 @@
|
||||
/* <== définition Multi-Select ==> */
|
||||
|
||||
/**
|
||||
* Permet d'afficher un sélecteur multiple d'options.
|
||||
* Pour chaque option cela affichera un checkbox.
|
||||
* Les options peuvent être regroupées dans des optgroup.
|
||||
*
|
||||
*
|
||||
* Utilisation :
|
||||
* <multi-select>
|
||||
<optgroup label="Groupe A">
|
||||
<option value="val1">Option 1</option>
|
||||
<option value="val2">Option 2</option>
|
||||
</optgroup>
|
||||
<optgroup label="Groupe B">
|
||||
<option value="valB1">Option B1</option>
|
||||
<option value="valB2">Option B2</option>
|
||||
</optgroup>
|
||||
</multi-select>
|
||||
|
||||
<multi-select>.values() => ["val1",...]
|
||||
<multi-select>.values(["val1",...]) => // sélectionne les options correspondantes (ne vérifie pas les options "single")
|
||||
<multi-select>.on("change", (values) => {}) => // écoute le changement de valeur
|
||||
*/
|
||||
|
||||
class MultiSelect extends HTMLElement {
|
||||
static formAssociated = true;
|
||||
|
||||
get form() {
|
||||
return this._internals.form;
|
||||
}
|
||||
get name() {
|
||||
return this.getAttribute("name");
|
||||
}
|
||||
|
||||
get label() {
|
||||
return this.getAttribute("label");
|
||||
}
|
||||
|
||||
set label(value) {
|
||||
this.setAttribute("label", value);
|
||||
}
|
||||
|
||||
get type() {
|
||||
return this.localName;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.attachShadow({ mode: "open" });
|
||||
|
||||
// HTML/CSS du composant
|
||||
this.shadowRoot.innerHTML = `
|
||||
<style>
|
||||
*{
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border-radius: 10px;
|
||||
user-select: none;
|
||||
}
|
||||
.dropdown-button {
|
||||
// padding: 10px;
|
||||
// background-color: #f1f1f1;
|
||||
// border: 1px solid #ccc;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
.dropdown-content {
|
||||
display: none;
|
||||
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;
|
||||
width: 100%;
|
||||
}
|
||||
.dropdown-content .optgroup div {
|
||||
font-weight: bold;
|
||||
}
|
||||
.dropdown-content .option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.dropdown-content .option input[type="checkbox"] {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
label.selected{
|
||||
background-color: #C2DBFB;
|
||||
}
|
||||
label{
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
label:hover{
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
</style>
|
||||
<div class="dropdown">
|
||||
<button class="dropdown-button">Select options</button>
|
||||
<div class="dropdown-content multi-select-container"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
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);
|
@ -7,7 +7,6 @@
|
||||
|
||||
{% block scripts %}
|
||||
{{ super() }}
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/purl.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/js/etud_info.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/js/groups_view.js"></script>
|
||||
@ -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() }}
|
||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap-theme.min.css">
|
||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap-multiselect-1.1.2/bootstrap-multiselect.min.css">
|
||||
{# <link rel="stylesheet" href="{{scu.STATIC_DIR}}/libjs/bootstrap/css/bootstrap.min.css"> #}
|
||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/assiduites.css">
|
||||
<link rel="stylesheet" href="{{scu.STATIC_DIR}}/css/minitimeline.css">
|
||||
|
||||
@ -153,12 +153,12 @@
|
||||
<div class="infos">
|
||||
<div class="infos-button">Groupes : {{grp|safe}}</div>
|
||||
<div>
|
||||
<button class="btn_date" onclick="jourSuivant(true)">
|
||||
<button class="btn_date btn btn-secondary" onclick="jourSuivant(true)">
|
||||
⇤
|
||||
</button>
|
||||
<input type="text" name="date" id="date" class="datepicker" value="{{date}}">
|
||||
</div>
|
||||
<button class="btn_date" onclick="jourSuivant(false)">
|
||||
<button class="btn_date btn btn-secondary" onclick="jourSuivant(false)">
|
||||
⇥
|
||||
</button>
|
||||
</div>
|
||||
@ -166,8 +166,8 @@
|
||||
<div style="display: {{'none' if readonly == 'true' else 'block'}};">
|
||||
{{timeline|safe}}
|
||||
<div>
|
||||
<button onclick="setPeriodValues(t_start, t_mid)">Matin</button>
|
||||
<button onclick="setPeriodValues(t_mid, t_end)">Après-Midi</button>
|
||||
<button class=" btn btn-secondary" onclick="setPeriodValues(t_start, t_mid)">Matin</button>
|
||||
<button class=" btn btn-secondary" onclick="setPeriodValues(t_mid, t_end)">Après-Midi</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -33,6 +33,7 @@
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/timepicker-1.3.5/jquery.timepicker.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/qtip/jquery.qtip-3.0.3.min.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/libjs/purl.js"></script>
|
||||
<script src="{{scu.STATIC_DIR}}/js/multi-select.js"></script>
|
||||
<script>
|
||||
const SCO_TIMEZONE = "{{ scu.TIME_ZONE }}";
|
||||
</script>
|
||||
|
@ -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(),
|
||||
|
@ -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),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user