diff --git a/core/static/webpack/ajax-select-index.ts b/core/static/webpack/ajax-select-index.ts index e64df216..c98fc48b 100644 --- a/core/static/webpack/ajax-select-index.ts +++ b/core/static/webpack/ajax-select-index.ts @@ -1,41 +1,89 @@ import "tom-select/dist/css/tom-select.css"; import { inheritHtmlElement, registerComponent } from "#core:utils/web-components"; import TomSelect from "tom-select"; -import type { TomItem, TomLoadCallback, TomOption } from "tom-select/dist/types/types"; +import type { + RecursivePartial, + TomItem, + TomLoadCallback, + TomOption, + TomSettings, +} from "tom-select/dist/types/types"; import type { escape_html } from "tom-select/dist/types/utils"; import { type UserProfileSchema, userSearchUsers } from "#openapi"; -@registerComponent("ajax-select") -export class AjaxSelect extends inheritHtmlElement("select") { +abstract class AjaxSelectBase extends inheritHtmlElement("select") { + static observedAttributes = ["delay", "placeholder", "max"]; public widget: TomSelect; - public filter?: (items: T[]) => T[]; + + private delay: number | null = null; + private placeholder = ""; + private max: number | null = null; + + attributeChangedCallback(name: string, _oldValue?: string, newValue?: string) { + switch (name) { + case "delay": { + this.delay = Number.parseInt(newValue) ?? null; + break; + } + case "placeholder": { + this.placeholder = newValue ?? ""; + break; + } + case "max": { + this.max = Number.parseInt(newValue) ?? null; + break; + } + default: { + return; + } + } + } constructor() { super(); window.addEventListener("DOMContentLoaded", () => { - this.loadTomSelect(); + this.configureTomSelect(this.defaultTomSelectSettings()); + this.setDefaultTomSelectBehaviors(); }); } - loadTomSelect() { + private defaultTomSelectSettings(): RecursivePartial { + return { + maxItems: this.max, + loadThrottle: this.delay, + placeholder: this.placeholder, + }; + } + + private setDefaultTomSelectBehaviors() { + // Allow removing selected items by clicking on them + this.widget.on("item_select", (item: TomItem) => { + this.widget.removeItem(item); + }); + // Remove typed text once an item has been selected + this.widget.on("item_add", () => { + this.widget.setTextboxValue(""); + }); + } + + abstract configureTomSelect(defaultSettings: RecursivePartial): void; +} + +@registerComponent("user-ajax-select") +export class UserAjaxSelect extends AjaxSelectBase { + public filter?: (items: T[]) => T[]; + + configureTomSelect(defaultSettings: RecursivePartial) { const minCharNumberForSearch = 2; - let maxItems = 1; - - if (this.node.multiple) { - maxItems = Number.parseInt(this.node.dataset.max) ?? null; - } - this.widget = new TomSelect(this.node, { + ...defaultSettings, hideSelected: true, diacritics: true, duplicates: false, - maxItems: maxItems, - loadThrottle: Number.parseInt(this.node.dataset.delay) ?? null, valueField: "id", labelField: "display_name", - searchField: ["display_name", "nick_name", "first_name", "last_name"], - placeholder: this.node.dataset.placeholder ?? "", + searchField: [], // Disable local search filter and rely on tested backend shouldLoad: (query: string) => { return query.length >= minCharNumberForSearch; // Avoid launching search with less than 2 characters }, @@ -80,14 +128,5 @@ export class AjaxSelect extends inheritHtmlElement("select") { }, }, }); - - // Allow removing selected items by clicking on them - this.widget.on("item_select", (item: TomItem) => { - this.widget.removeItem(item); - }); - // Remove typed text once an item has been selected - this.widget.on("item_add", () => { - this.widget.setTextboxValue(""); - }); } } diff --git a/sas/templates/sas/picture.jinja b/sas/templates/sas/picture.jinja index 43651383..965164fa 100644 --- a/sas/templates/sas/picture.jinja +++ b/sas/templates/sas/picture.jinja @@ -157,12 +157,12 @@
{% trans %}People{% endtrans %}
{% if user.was_subscribed %}
- + delay="300" + placeholder="{%- trans -%}Identify users on pictures{%- endtrans -%}" + >
{% endif %}