Add AutoCompleteSelectGroup

This commit is contained in:
Antoine Bartuccio 2024-10-20 13:33:44 +02:00
parent 0af3505c2a
commit 8bbebfdb13
4 changed files with 91 additions and 20 deletions

View File

@ -9,7 +9,12 @@ import type {
TomSettings,
} from "tom-select/dist/types/types";
import type { escape_html } from "tom-select/dist/types/utils";
import { type UserProfileSchema, userSearchUsers } from "#openapi";
import {
type GroupSchema,
type UserProfileSchema,
groupSearchGroup,
userSearchUsers,
} from "#openapi";
@registerComponent("autocomplete-select")
class AutocompleteSelect extends inheritHtmlElement("select") {
@ -56,14 +61,9 @@ class AutocompleteSelect extends inheritHtmlElement("select") {
connectedCallback() {
super.connectedCallback();
// Collect all options nodes and put them into the select node
const options: Element[] = []; // We need to make a copy to delete while iterating
for (const child of this.children) {
if (child.tagName.toLowerCase() === "option") {
options.push(child);
}
}
for (const option of options) {
for (const option of Array.from(this.children).filter(
(child) => child.tagName.toLowerCase() === "option",
)) {
this.removeChild(option);
this.node.appendChild(option);
}
@ -158,6 +158,18 @@ abstract class AjaxSelect extends AutocompleteSelect {
},
};
}
protected attachBehaviors() {
super.attachBehaviors();
// Gather selected options, they must be added with slots like `<slot>json</slot>`
for (const value of Array.from(this.children)
.filter((child) => child.tagName.toLowerCase() === "slot")
.map((slot) => JSON.parse(slot.innerHTML))) {
this.widget.addOption(value, true);
this.widget.addItem(value[this.valueField]);
}
}
}
@registerComponent("user-ajax-select")
@ -172,9 +184,6 @@ export class UserAjaxSelect extends AjaxSelect {
}
return [];
}
protected tomSelectSettings(): RecursivePartial<TomSettings> {
return super.tomSelectSettings();
}
protected renderOption(item: UserProfileSchema, sanitize: typeof escape_html) {
return `<div class="select-item">
@ -191,3 +200,27 @@ export class UserAjaxSelect extends AjaxSelect {
return `<span><i class="fa fa-times"></i>${sanitize(item.display_name)}</span>`;
}
}
@registerComponent("group-ajax-select")
export class GroupsAjaxSelect extends AjaxSelect {
protected valueField = "id";
protected labelField = "name";
protected async search(query: string): Promise<TomOption[]> {
const resp = await groupSearchGroup({ query: { search: query } });
if (resp.data) {
return resp.data.results;
}
return [];
}
protected renderOption(item: GroupSchema, sanitize: typeof escape_html) {
return `<div class="select-item">
<span class="select-item-text">${sanitize(item.name)}</span>
</div>`;
}
protected renderItem(item: GroupSchema, sanitize: typeof escape_html) {
return `<span><i class="fa fa-times"></i>${sanitize(item.name)}</span>`;
}
}

View File

@ -7,4 +7,5 @@
<optgroup label="{{ group_name }}">{% endif %}{% for widget in group_choices %}
{% include widget.template_name %}{% endfor %}{% if group_name %}
</optgroup>{% endif %}{% endfor %}
{% for sel in selected %}<slot style="display: none">{{ sel }}</slot>{% endfor %}
</{{ component }}>

View File

@ -1,11 +1,27 @@
from django.contrib.staticfiles.storage import staticfiles_storage
from django.db.models import Model
from django.forms import Select, SelectMultiple
from ninja import ModelSchema
from core.models import Group, User
from core.schemas import GroupSchema, UserProfileSchema
class AutoCompleteSelectMixin:
component_name = "autocomplete-select"
template_name = "core/widgets/autocomplete_select.jinja"
is_ajax = False
model: Model | None = None
schema: ModelSchema | None = None
pk = "id"
def __init__(self, attrs=None, choices=()):
if self.is_ajax:
choices = () # Avoid computing anything when in ajax mode
super().__init__(attrs=attrs, choices=choices)
@property
def is_ajax(self):
return self.model and self.schema
def optgroups(self, name, value, attrs=None):
"""Don't create option groups when doing ajax"""
@ -27,6 +43,13 @@ class AutoCompleteSelectMixin:
staticfiles_storage.url("core/components/ajax-select.scss"),
],
}
if self.is_ajax:
context["selected"] = [
self.schema.from_orm(obj).json()
for obj in self.model.objects.filter(
**{f"{self.pk}__in": context["widget"]["value"]}
).all()
]
return context
@ -38,9 +61,23 @@ class AutoCompleteSelectMultiple(AutoCompleteSelectMixin, SelectMultiple): ...
class AutoCompleteSelectUser(AutoCompleteSelectMixin, Select):
component_name = "user-ajax-select"
is_ajax = True
model = User
schema = UserProfileSchema
class AutoCompleteSelectMultipleUser(AutoCompleteSelectMixin, SelectMultiple):
component_name = "user-ajax-select"
is_ajax = True
model = User
schema = UserProfileSchema
class AutoCompleteSelectGroup(AutoCompleteSelectMixin, Select):
component_name = "group-ajax-select"
model = Group
schema = GroupSchema
class AutoCompleteSelectMultipleGroup(AutoCompleteSelectMixin, SelectMultiple):
component_name = "group-ajax-select"
model = Group
schema = GroupSchema

View File

@ -15,7 +15,7 @@ from core.views.forms import SelectDateTime
from core.views.widgets.markdown import MarkdownInput
from core.views.widgets.select import (
AutoCompleteSelect,
AutoCompleteSelectMultiple,
AutoCompleteSelectMultipleGroup,
AutoCompleteSelectUser,
)
from election.models import Candidature, Election, ElectionList, Role, Vote
@ -157,10 +157,10 @@ class ElectionForm(forms.ModelForm):
"candidature_groups",
]
widgets = {
"edit_groups": AutoCompleteSelectMultiple,
"view_groups": AutoCompleteSelectMultiple,
"vote_groups": AutoCompleteSelectMultiple,
"candidature_groups": AutoCompleteSelectMultiple,
"edit_groups": AutoCompleteSelectMultipleGroup,
"view_groups": AutoCompleteSelectMultipleGroup,
"vote_groups": AutoCompleteSelectMultipleGroup,
"candidature_groups": AutoCompleteSelectMultipleGroup,
}
start_date = forms.DateTimeField(