From be5ce414ba98bbb928c1d655cb53c5d1fb8e3fa2 Mon Sep 17 00:00:00 2001 From: Sli Date: Sun, 20 Oct 2024 16:57:38 +0200 Subject: [PATCH] Add proper delete button and fix item ordering --- core/static/core/components/ajax-select.scss | 5 +- .../core/components/ajax-select-index.ts | 76 ++++++++++++------- 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/core/static/core/components/ajax-select.scss b/core/static/core/components/ajax-select.scss index 8005b52d..4d0d1d65 100644 --- a/core/static/core/components/ajax-select.scss +++ b/core/static/core/components/ajax-select.scss @@ -25,10 +25,6 @@ .ts-control { .item { - .fa-times { - margin-left: 5px; - margin-right: 5px; - } cursor: pointer; background-color: #e4e4e4; @@ -39,6 +35,7 @@ margin-top: 5px; margin-bottom: 5px; padding-right: 10px; + padding-left: 10px; } } \ No newline at end of file diff --git a/core/static/webpack/core/components/ajax-select-index.ts b/core/static/webpack/core/components/ajax-select-index.ts index 42875544..2bbb6763 100644 --- a/core/static/webpack/core/components/ajax-select-index.ts +++ b/core/static/webpack/core/components/ajax-select-index.ts @@ -3,7 +3,6 @@ import { inheritHtmlElement, registerComponent } from "#core:utils/web-component import TomSelect from "tom-select"; import type { RecursivePartial, - TomItem, TomLoadCallback, TomOption, TomSettings, @@ -71,14 +70,25 @@ class AutocompleteSelect extends inheritHtmlElement("select") { this.attachBehaviors(); } + protected shouldLoad(query: string) { + console.log(this); + return query.length >= this.minCharNumberForSearch; // Avoid launching search with less than setup number of characters + } + protected tomSelectSettings(): RecursivePartial { return { + plugins: { + // biome-ignore lint/style/useNamingConvention: this is required by the api + remove_button: { + title: gettext("Remove"), + }, + }, + persist: false, maxItems: this.node.multiple ? this.max : 1, + closeAfterSelect: true, loadThrottle: this.delay, placeholder: this.placeholder, - shouldLoad: (query: string) => { - return query.length >= this.minCharNumberForSearch; // Avoid launching search with less than 2 characters - }, + shouldLoad: (query: string) => this.shouldLoad(query), // wraps the method to avoid shadowing `this` by the one from tom-select render: { option: (item: TomOption, sanitize: typeof escape_html) => { return `
@@ -86,7 +96,7 @@ class AutocompleteSelect extends inheritHtmlElement("select") {
`; }, item: (item: TomOption, sanitize: typeof escape_html) => { - return `${sanitize(item.text)}`; + return `${sanitize(item.text)}`; }, // biome-ignore lint/style/useNamingConvention: that's how it's defined not_loading: (data: TomOption, _sanitize: typeof escape_html) => { @@ -101,14 +111,7 @@ class AutocompleteSelect extends inheritHtmlElement("select") { } protected attachBehaviors() { - // 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(""); - }); + /* Called once the widget has been initialized */ } } @@ -118,6 +121,8 @@ abstract class AjaxSelect extends AutocompleteSelect { protected abstract valueField: string; protected abstract labelField: string; + protected abstract searchField: string[]; + protected abstract renderOption( item: TomOption, sanitize: typeof escape_html, @@ -129,16 +134,28 @@ abstract class AjaxSelect extends AutocompleteSelect { this.filter = filter; } - protected getLoadFunction() { - // this will be replaced by TomSelect if we don't wrap it that way - return async (query: string, callback: TomLoadCallback) => { - const resp = await this.search(query); - if (this.filter) { - callback(this.filter(resp), []); - } else { - callback(resp, []); - } - }; + protected shouldLoad(query: string) { + const resp = super.shouldLoad(query); + /* Force order sync with backend if no client side filtering is set */ + if (!resp && this.searchField.length === 0) { + this.widget.clearOptions(); + } + return resp; + } + + protected async loadFunction(query: string, callback: TomLoadCallback) { + /* Force order sync with backend if no client side filtering is set */ + if (this.searchField.length === 0) { + this.widget.clearOptions(); + } + + const resp = await this.search(query); + + if (this.filter) { + callback(this.filter(resp), []); + } else { + callback(resp, []); + } } protected tomSelectSettings(): RecursivePartial { @@ -149,8 +166,9 @@ abstract class AjaxSelect extends AutocompleteSelect { duplicates: false, valueField: this.valueField, labelField: this.labelField, - searchField: [], // Disable local search filter and rely on tested backend - load: this.getLoadFunction(), + searchField: this.searchField, + load: (query: string, callback: TomLoadCallback) => + this.loadFunction(query, callback), // wraps the method to avoid shadowing `this` by the one from tom-select render: { ...super.tomSelectSettings().render, option: this.renderOption, @@ -166,7 +184,7 @@ abstract class AjaxSelect extends AutocompleteSelect { 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.addOption(value, false); this.widget.addItem(value[this.valueField]); } } @@ -176,6 +194,7 @@ abstract class AjaxSelect extends AutocompleteSelect { export class UserAjaxSelect extends AjaxSelect { protected valueField = "id"; protected labelField = "display_name"; + protected searchField: string[] = []; // Disable local search filter and rely on tested backend protected async search(query: string): Promise { const resp = await userSearchUsers({ query: { search: query } }); @@ -197,7 +216,7 @@ export class UserAjaxSelect extends AjaxSelect { } protected renderItem(item: UserProfileSchema, sanitize: typeof escape_html) { - return `${sanitize(item.display_name)}`; + return `${sanitize(item.display_name)}`; } } @@ -205,6 +224,7 @@ export class UserAjaxSelect extends AjaxSelect { export class GroupsAjaxSelect extends AjaxSelect { protected valueField = "id"; protected labelField = "name"; + protected searchField = ["name"]; protected async search(query: string): Promise { const resp = await groupSearchGroup({ query: { search: query } }); @@ -221,6 +241,6 @@ export class GroupsAjaxSelect extends AjaxSelect { } protected renderItem(item: GroupSchema, sanitize: typeof escape_html) { - return `${sanitize(item.name)}`; + return `${sanitize(item.name)}`; } }